Compare commits

...
143 Commits
Author SHA1 Message Date
Pedro Pinto d8b8a5d710 Updated release notes. 2019-07-23 15:44:49 -04:00
Pedro Pinto 5536f5f585 Cover should always detach from hedges when completing. 2019-07-23 15:04:11 -04:00
Kevin Darby c8badf5689 Merged in fix_span_error_message (pull request #306)
guard against UI setting cash and leverage factors for accounts I don't know about when span is off

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-17 14:51:23 +00:00
Kevin Darby 58533bf16f guard against UI setting cash and leverage factors for accounts I don't know about when span is off 2019-07-17 10:48:16 -04:00
Pedro Pinto 6b3c3ded68 Fixed up release notes. 2019-07-17 10:24:51 -04:00
Pedro Pinto d65e00fade Merged in SP-132-make-algo-shutdown-orderly (pull request #305)
RTM. SP-132 make algo shutdown orderly
2019-07-17 14:23:09 +00:00
Pedro Pinto 68e5d07aef Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-07-17 09:36:15 -04:00
Pedro Pinto 68345a181e Updated release notes. 2019-07-17 09:35:43 -04:00
Kevin Darby 69c121a19c Merged in fix_span (pull request #301)
fix crash where account name > 12 chars, tested with long account name

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-17 13:32:33 +00:00
Pedro Pinto 780ce7416f Merge branch 'master' into SP-132-make-algo-shutdown-orderly 2019-07-17 09:07:54 -04:00
Pedro Pinto 9fe338a62a Updated release notes. 2019-07-17 09:07:24 -04:00
Jake Norbut f027af66c8 Merged in ob_price_filter_tick_comparison (pull request #304)
RTM - Ob price filter tick comparison

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2019-07-16 21:43:23 +00:00
Jake Norbut 6b2708b160 PR suggestions 2019-07-16 16:34:59 -05:00
Jake Norbut 534474621f price filter should use new add_price tick-based method 2019-07-16 15:51:27 -05:00
Jake Norbut 554614ab72 add means to perform price comparsion based on tick equality 2019-07-16 15:50:44 -05:00
Pedro Pinto 2ede69a263 Updated release notes. 2019-07-16 16:21:31 -04:00
Pedro Pinto 9def94aceb Changed IB to cancel rather than error if it sees a child cancel.
This allows IB to remain agnostic of wheter the child cancel was a
simply a consequence of the IB itself shutting down, in which case it
would be ignored. If the IB instead errorred out than that error
superseeds the cancel and results in the IB reporting an error.
2019-07-16 15:55:28 -04:00
Pedro Pinto 0af30b3cc0 Changed cover to handle fills while canceling. 2019-07-16 15:32:38 -04:00
Taha Afzal a91c2d9bc6 SP-146 (Limiter will not accept unresolved fills) 2019-07-16 11:20:15 -04:00
Pedro Pinto 481c1fd2de Fixed gcc build. 2019-07-16 10:29:59 -04:00
Pedro Pinto e80fc807e0 Moved all reporting state calls to top level event handler.
Any CPU the algo order receives must originate from a top level event,
its simpler to consolidate all report guards in those events, instead
of chasing every call that comes out of the algo.
2019-07-16 10:20:30 -04:00
Pedro Pinto c8e054d079 Added algo wrapper documentation. 2019-07-16 10:10:46 -04:00
Pedro Pinto 9480e76d4b Cleaned up algo wrapper 2019-07-16 10:10:11 -04:00
Pedro Pinto 538411353a Improved cover to hedge while shutting down. 2019-07-16 08:48:36 -04:00
Pedro Pinto da6998c391 Do not error out when receiving an udpate from an unknown child.
Because the child is presumably a detached child.
2019-07-15 16:43:39 -04:00
Pedro Pinto 60786b105c Handled terminate_error while shutting down. 2019-07-15 16:26:22 -04:00
Pedro Pinto f488248abf Fixed handling of child update while shutdown. 2019-07-15 16:03:36 -04:00
Pedro Pinto 830b590e5b Algos now go through an orderly shutdown phase.
During which they can still process child updates.
2019-07-15 10:59:13 -04:00
Kevin Darby c9c93b3cf0 Merged in fix_span1 (pull request #300)
fix crash when acct names are > 12 chars

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-12 18:58:13 +00:00
Kevin Darby 9e5d43f44a fix crash where account name > 12 chars, tested with long account name 2019-07-11 17:53:48 -04:00
Kevin Darby 5c1965f534 fix crash when acct names are > 12 chars 2019-07-11 14:07:31 -04:00
Pedro Pinto 8553229119 Merge branch 'master' into prod 2019-07-11 10:45:10 -04:00
Pedro Pinto 4903c12533 _index.md edited online with Bitbucket 2019-07-11 14:23:13 +00:00
Jake Norbut 88f23530c6 Merged in SP-119 (pull request #298)
RTM - SP-119 Ladder settings NullRef

Approved-by: John Colvin <john.colvin@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-11 14:18:45 +00:00
Jake Norbut 76ff36e514 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SP-119 2019-07-09 11:54:46 -05:00
Jake Norbut 740c14c261 add LadderSettings santization on paths to VM instantiation 2019-07-09 11:52:41 -05:00
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
Jake Norbut dbdc915ff2 populating mini_ob (LadderMiniOrderBookSettings) in LadderSettings.Default 2019-07-08 14:24:36 -05:00
Jake Norbut 10476c6662 null ref check on LadderSettings.mini_ob.value 2019-07-08 14:22:55 -05: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
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
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
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
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
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
Kevin Darby 159e8fba23 beginnings of span, should't be long 2019-06-12 18:17:09 -04:00
98 changed files with 2131 additions and 677 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/*
+45 -2
View File
@@ -4,6 +4,48 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.7.1
### User Visible Changes
- Fixed some issues with the Cover algo, where in some circumstances
it would erroneously cancel launched hedges on completion.
## 2.7.0
### User Visible Changes
- Allowed algos to process "in-flight" fills while shutting down
- Changed the cover algo to continue to hedge while shutting down
### API Changes
- Changed the API so that algos do not fully terminate until all
their children are terminated. Algos that are in the process of
shutting down in response to a complete or cancel call will still
receive child updates, which they can use to calculate and report
an accurate fill count (during this shutdown stage algos cannot
create, cancel or update children). A new IAlgoServices method,
is_shutting_down(), can be used to determine if an algo is
shutting down.
## 2.6.2
### User Visible Changes
- Fixed an issue in the ladder where in some cases the mini order book could not be brought up from a badge
- Fixed a rare race condition that could lead to erroneous limit enforcement
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
## 2.6.1
### User Visible Changes
- Fixed an issue in the ladder where an error could occur when trying to change ladder settings for the first time
## 2.6.0
### User Visible Changes
- In the ladder made last trade more distinctive when overlayed on the volumn column
- In the ladder, corrected display of volume tool tip
- In the ladder, 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
@@ -293,7 +335,7 @@ insert_anchor_links = "right"
- The zoom slider can be enabled from Ladder Form -> Settings button -> Left Pane tab -> check Zoom Slider checkbox
- The zoom functionality can also be performed via hotkey and mouse wheel combinations
- Zoom In = CTRL + Mouse wheel up
- Zoom Out = CTRL + Mouse wheel down
- Zoom Out = CTRL + Mouse wheel down␍
- Reset zoom = CTRL + Mouse wheel click or double click on the zoom slider
### Exchange simulator
- Improved the exchange simulator to track real market data more closely
@@ -398,7 +440,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
@@ -339,6 +339,7 @@ namespace mbts.spark {
zoom_slider_visible = false,
zoom_factor = _default_zoom_factor,
net_change = NetChangeTerms.None,
mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general),
};
rv.row_height = LadderRender.measure_row_height(rv);
return rv;
@@ -113,7 +113,7 @@ namespace bts.shell {
});
});
// vm
vm = LadderVM.create(_bus, model, last_value ?? _settings_hub.current.ladder, settings_source);
vm = LadderVM.create(_bus, model, LadderSettings.sanitize(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;
@@ -363,7 +363,9 @@ namespace mbts.spark {
} 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));
clone.mini_ob = new LadderMiniOrderBookSettings(_current.mini_ob.value != null ?
mini_ob.create().with(sorting_criterion: _current.mini_ob.value.sorting_criterion) :
mini_ob.create());
#pragma warning restore CS0612 // Type or member is obsolete
}
clone.badge_style = badge_style;
@@ -51,7 +51,7 @@ namespace mbts.spark {
LadderVM.create_readonly(_bus,
new LadderModel(security_md.Value, _model, _model.mbo_service,
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
Observable.Return(LadderSettings.Default)) : null);
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null);
RaisePropertyChanged(nameof(value));
}
}
@@ -694,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();
}
}
@@ -215,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,6 +259,7 @@ namespace mbts.spark {
if (settings.show_ltq && last_trade_here) {
numbers_text.Clear();
numbers_text.Add(last_size_and_direction);
text_brush = BTSCoreColors.white;
}
if (numbers_text.Count > 0) {
string str = string.Join("/", numbers_text);
@@ -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) }
};
}
@@ -144,7 +144,7 @@ namespace bts.shell.orderbook {
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
// numeric
pb.add_numeric(fs.qty, o => o.order.quantity());
pb.add_numeric(fs.price, o => o.order.price());
pb.add_price(fs.price, o => o.order.price(), o => o.security);
pb.add_numeric(fs.qty_filled, o => o.order.filled());
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
pb.add_numeric(fs.last_price, o => o.order.last_price());
+6 -4
View File
@@ -602,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,
@@ -619,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,
@@ -715,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) &&
@@ -906,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;
@@ -37,6 +37,51 @@ namespace mbts.spark.filters {
_predicates.Add(impl);
bool impl(TRow o) => max.CompareTo(selector(o)) >= 0;
}
}
// this is a copy of add_numeric except we do a tick_equals comparsion
// if the ticks are not equals, then it falls back to a regular double comparison
//
// if the SecurityImmutable? doesn't have a value
// then we fallback to double comparison
public void add_price(DoubleMinMaxState value, Func<TRow, double> selector, Func<TRow, SecurityImmutable?> si_selector) {
if (value == null)
return;
if (value.minimum.active) {
var min = value.minimum.value.Value;
if (value.maximum.active) {
var max = value.maximum.value.Value;
_predicates.Add(impl);
bool impl(TRow o) {
var v = selector(o);
var si = si_selector(o);
if (si.HasValue) {
return si.Value.ContractSpec.compare_tick_fallback_to_double(min, v) <= 0 &&
si.Value.ContractSpec.compare_tick_fallback_to_double(max, v) >= 0;
}
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
}
} else {
_predicates.Add(impl);
bool impl(TRow o) {
var si = si_selector(o);
if (si.HasValue) {
return si.Value.ContractSpec.compare_tick_fallback_to_double(min, selector(o)) <= 0;
}
return min.CompareTo(selector(o)) <= 0;
}
}
} else if (value.maximum.active) {
var max = value.maximum.value.Value;
_predicates.Add(impl);
bool impl(TRow o) {
var si = si_selector(o);
if (si.HasValue) {
return si.Value.ContractSpec.compare_tick_fallback_to_double(max, selector(o)) >= 0;
}
return max.CompareTo(selector(o)) >= 0;
}
}
}
public void add_regex(string str, Func<TRow, string> selector) {
@@ -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}"
@@ -128,6 +128,9 @@ namespace mbts.spark {
public static IEqualityComparer<double> tick_equality_comparer(this ContractSpecificationImmutable contract) =>
EqualityComparer.Create((double a, double b) => tick_equals(contract, a, b), x => MathUtils.RoundFractionalUnits(x, contract.LowTick));
public static int compare_tick_fallback_to_double(this ContractSpecificationImmutable contract, double x, double y) =>
tick_equals(contract, x, y) ? 0 : x.CompareTo(y);
public static double round_to_tick(this ContractSpecificationImmutable spec, double value) {
double intpart = (long)value;
double floatpart = value % 1;
+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;
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
+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;
}
+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 {
@@ -32,6 +32,7 @@ message PNLUnit{
message AccountPNL{
uint32 account_id = 1 [(bts.api.key)=true];
string account_name = 10;
double day_pnl = 20;
}
+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];
}
}
+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
+21 -4
View File
@@ -1,12 +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"
@@ -15,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 {
@@ -53,6 +53,7 @@ struct ServiceBridge::PImpl {
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;
@@ -85,6 +86,7 @@ void ServiceBridge::init() {
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();
}
@@ -236,6 +238,21 @@ void ServiceBridge::update_account_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;
}
+3
View File
@@ -1,5 +1,6 @@
#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/md/SecurityOracle.h"
@@ -77,6 +78,8 @@ 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;
@@ -5,6 +5,7 @@
#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"
@@ -55,6 +56,9 @@ void OrderManager::init() {
[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) {
@@ -77,10 +81,18 @@ 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_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(
@@ -35,6 +35,7 @@ class CreateFIXBufferReplyEvent;
class GatewayStatusChangeEvent;
class NewAdminDataEvent;
class AccountPnLUpdateEvent;
class ExcessLiquidityUpdateEvent;
struct SessionServicesData;
class DynamicOrderRegistry;
} // namespace orderman
@@ -85,6 +86,7 @@ class OrderManager : public mercury::Actor, public mercury::IPollableActor {
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,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
+10 -2
View File
@@ -32,11 +32,13 @@ void LimitStore::copy_data_from(LimitStore &source) {
if (source_security_limit_tree != nullptr)
it->second.copy_positions_from(*source_security_limit_tree);
}
// copy pnl's
// 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())
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){
@@ -44,6 +46,12 @@ void LimitStore::set_pnl (AccountID account_id, double pnl){
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,
const bts::api::admin::AdminData &admin_data) {
+49 -7
View File
@@ -10,6 +10,9 @@
#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 {
@@ -39,10 +42,12 @@ private:
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())
curr_day_pnl (os::nan()),
excess_liquidity (os::nan())
{}
};
const bts::api::admin::AdminData _admin_data;
@@ -107,6 +112,7 @@ 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
@@ -152,7 +158,13 @@ public:
Quantity quantity,
bool enforce_clip = true) const{
Limiter::LimiterQueryResult rv; rv.result = Limiter::OK;
_loss_limit(account_id, rv);
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());
@@ -183,10 +195,13 @@ public:
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_id, rv);
_loss_limit(account_ptr, rv);
if(!rv.ok())
return rv;
const SecurityLimitTree* sec_tree = _find_security(security.id());
@@ -226,16 +241,15 @@ private:
void _add_product_entry(const api::Security& security,
const bts::api::admin::AdminData& admin_data);
void _set_user_data();
void _loss_limit(AccountID account_id,
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
const auto acct_it = _account_data_map.find(account_id.value());
if(acct_it != _account_data_map.end()){
const auto& account_data = acct_it->second;
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()){
@@ -260,5 +274,33 @@ private:
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";
}
}
};
}}
+9
View File
@@ -78,6 +78,15 @@ void Limiter::set_day_pnl (AccountID account_id, double pnl){
_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);
+4 -2
View File
@@ -34,7 +34,8 @@ class Limiter {
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,
@@ -47,7 +48,8 @@ class Limiter {
NO_SUCH_ACCOUNT,
USER_DEACTIVATED,
NO_SUCH_USER,
LOSS_LIMIT_BREACHED
LOSS_LIMIT_BREACHED,
MARGIN_LIMIT_BREACHED
};
struct LimiterQueryResult {
@@ -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;
}
+116 -20
View File
@@ -16,7 +16,13 @@ namespace orderman {
using namespace api::orderman;
template <class K, class V> using Map = boost::container::flat_map<K, V>;
enum class AlgoWrapperOrder::Status { INACTIVE, W_ATTACH, WORKING, COMPLETED };
enum class AlgoWrapperOrder::Status {
INACTIVE,
W_ATTACH,
WORKING,
SHUTTING_DOWN,
COMPLETED
};
class ChildTemplate {
public:
@@ -62,6 +68,7 @@ struct AlgoWrapperOrder::PImpl {
OrderKey attach_target = null_order_key();
std::optional<api::orderman::Update> pending_update;
std::optional<std::pair<OrderStatus, std::string>> pending_terminate;
OrderStatus external_status() const { return _state.status(); }
void set_external_status(OrderStatus s) {
@@ -222,6 +229,7 @@ OrderOpResult AlgoWrapperOrder::on_activate(Price p, Quantity q,
return {};
case Status::W_ATTACH:
case Status::SHUTTING_DOWN:
case Status::WORKING:
bts_assert(false);
}
@@ -238,6 +246,7 @@ OrderOpResult AlgoWrapperOrder::on_update(Update const &update) {
me->pending_update = update;
return {};
case Status::COMPLETED:
case Status::SHUTTING_DOWN:
return {};
case Status::WORKING: {
me->set_price_quantity(Price(update.price()),
@@ -275,6 +284,7 @@ orderman::OrderOpResult AlgoWrapperOrder::on_detach() {
switch (me->status) {
case Status::W_ATTACH:
case Status::WORKING:
case Status::SHUTTING_DOWN:
for (auto &[_, c] : me->children) {
(void)_;
c.order.mark_detached_from_parent();
@@ -309,6 +319,7 @@ orderman::OrderOpResult AlgoWrapperOrder::on_cancel() {
break;
}
break;
case Status::SHUTTING_DOWN:
case Status::COMPLETED:
break;
case Status::INACTIVE:
@@ -347,13 +358,20 @@ Quantity AlgoWrapperOrder::total_fills() const {
return Quantity(me->state().filled_quantity());
}
bool AlgoWrapperOrder::is_shutting_down() const {
return me->status == Status::SHUTTING_DOWN;
}
void AlgoWrapperOrder::subscribe_book(SecurityKey k) {
if (!is_working())
return;
bts_assert(me->book_subs.find(k) == me->book_subs.end());
me->book_subs[k] = me->services->subscribe_to_book(
k, [this, k](auto const &ev) { me->algo->on_book(k, ev.book); });
k, [this, k](auto const &ev) {
ReportStateGuard _(*this);
me->algo->on_book(k, ev.book);
});
}
void AlgoWrapperOrder::unsubscribe_book(SecurityKey k) {
@@ -392,7 +410,10 @@ void AlgoWrapperOrder::subscribe_timer() {
bts_assert(!me->has_timer_sub);
me->has_timer_sub = true;
me->services->subscribe_to_time(
[this](auto const &) { me->algo->on_timer(); });
[this](auto const &) {
ReportStateGuard _(*this);
me->algo->on_timer();
});
}
void AlgoWrapperOrder::unsubscribe_timer() {
@@ -485,10 +506,9 @@ void AlgoWrapperOrder::detach_all_children() {
}
void AlgoWrapperOrder::update_fill_state(Quantity q, Price p) {
if (!is_working())
if (!is_working() && !is_shutting_down())
return;
ReportStateGuard _(*this);
auto previous_qty = me->state().filled_quantity();
if (previous_qty > q.value()) {
LOG(OrderMan, Error,
@@ -510,35 +530,37 @@ void AlgoWrapperOrder::update_fill_state(Quantity q, Price p) {
void AlgoWrapperOrder::set_ladder_visibility(bool is_visible) {
if (!is_working())
return;
ReportStateGuard _(*this);
me->set_ladder_visibility(is_visible);
}
void AlgoWrapperOrder::cancel(std::string const &msg) {
if (!is_working())
return;
ReportStateGuard _(*this);
terminate(ORDER_STATUS_CANCELED, msg);
if (working_children_count() == 0)
terminate(ORDER_STATUS_CANCELED, msg);
else
shutdown(ORDER_STATUS_CANCELED, msg);
}
void AlgoWrapperOrder::terminate_error(std::string const &msg) {
if (!is_working() && me->status != Status::W_ATTACH)
if (!is_working() && !is_shutting_down() && me->status != Status::W_ATTACH)
return;
ReportStateGuard _(*this);
// If we attached and an error was signaled we do not want to
// cancel our child because that child might be a long running
// order with a good queue position that we want to preserve
if (is_attaching())
me->children.erase(me->attach_target);
terminate(ORDER_STATUS_ERROR, msg);
}
void AlgoWrapperOrder::complete() {
if (!is_working())
return;
ReportStateGuard _(*this);
terminate(ORDER_STATUS_COMPLETED);
if (working_children_count() == 0)
terminate(ORDER_STATUS_COMPLETED);
else
shutdown(ORDER_STATUS_COMPLETED);
}
// --------------------------------------------------------------------------------
@@ -573,6 +595,7 @@ void AlgoWrapperOrder::on_attach_complete(
break;
case Status::COMPLETED:
break;
case Status::SHUTTING_DOWN:
case Status::INACTIVE:
case Status::WORKING:
bts_assert(false);
@@ -587,6 +610,7 @@ void AlgoWrapperOrder::on_child_order_update(
switch (me->status) {
case Status::WORKING:
case Status::W_ATTACH:
case Status::SHUTTING_DOWN:
if (auto it = me->children.find(child_key); it != me->children.end()) {
auto &child_record = it->second;
switch (new_child_state.status()) {
@@ -625,16 +649,38 @@ void AlgoWrapperOrder::on_child_order_update(
break;
}
} else
terminate_error(
fmt::format("Unknown child order update: {}", child_key.value()));
return;
LOG(OrderMan, Info,
"AlgoOrder {} received update for presumably detached order {}. "
"Ignoring it",
key().value(), child_key.value());
break;
case Status::COMPLETED:
return;
case Status::INACTIVE:
bts_assert(false);
return;
}
bts_assert(false);
if (me->status == Status::SHUTTING_DOWN)
try_complete_shutdown();
}
void AlgoWrapperOrder::try_complete_shutdown() {
bts_assert(me->status == Status::SHUTTING_DOWN);
bts_assert(me->children.size());
bts_assert(me->pending_terminate.has_value());
if (working_children_count() == 0) {
LOG(OrderMan, Info, "AlgoOrder {} completed shutdown. Terminated",
key().value());
terminate(me->pending_terminate.value().first,
me->pending_terminate.value().second);
} else {
LOG(OrderMan, Info,
"AlgoOrder {} still has {} orders working. Shutdown still in "
"progress",
key().value(), working_children_count());
}
}
void AlgoWrapperOrder::setup_transitions() {
@@ -676,8 +722,21 @@ void AlgoWrapperOrder::setup_transitions() {
// From WORKING
me->transition_actions[{Status::WORKING, Status::COMPLETED}] = [this]() {
cleanup();
me->children.clear();
_transition(Status::COMPLETED);
};
me->transition_actions[{Status::WORKING, Status::SHUTTING_DOWN}] = [this]() {
cleanup();
_transition(Status::SHUTTING_DOWN);
};
// From SHUTTING_DOWN
me->transition_actions[{Status::SHUTTING_DOWN, Status::COMPLETED}] =
[this]() {
me->children.clear();
me->pending_terminate.reset();
_transition(Status::COMPLETED);
};
}
void AlgoWrapperOrder::check_invariants() {
@@ -697,6 +756,7 @@ void AlgoWrapperOrder::check_invariants() {
bts_assert(me->book_subs.empty());
bts_assert(me->children.empty());
bts_assert(!me->has_timer_sub);
bts_assert(!me->pending_terminate.has_value());
break;
case Status::WORKING:
@@ -704,10 +764,23 @@ void AlgoWrapperOrder::check_invariants() {
bts_assert(me->security);
bts_assert(me->external_status() == ORDER_STATUS_WORKING);
bts_assert(!me->pending_update.has_value());
bts_assert(!me->pending_terminate.has_value());
break;
case Status::SHUTTING_DOWN:
bts_assert(working_children_count());
bts_assert(me->services);
bts_assert(me->security);
bts_assert(me->external_status() == ORDER_STATUS_WORKING);
bts_assert(me->book_subs.empty());
bts_assert(!me->has_timer_sub);
bts_assert(!me->pending_update.has_value());
bts_assert(me->pending_terminate.has_value());
break;
case Status::COMPLETED:
bts_assert(me->children.empty());
bts_assert(!me->pending_update.has_value());
bts_assert(me->external_status() == ORDER_STATUS_CANCELED ||
me->external_status() == ORDER_STATUS_COMPLETED ||
me->external_status() == ORDER_STATUS_ERROR ||
@@ -715,6 +788,7 @@ void AlgoWrapperOrder::check_invariants() {
bts_assert(me->book_subs.empty());
bts_assert(!me->has_timer_sub);
bts_assert(!me->pending_update.has_value());
bts_assert(!me->pending_terminate.has_value());
break;
}
}
@@ -745,15 +819,25 @@ void AlgoWrapperOrder::terminate(api::orderman::OrderStatus st,
transition(Status::COMPLETED);
}
void AlgoWrapperOrder::shutdown(api::orderman::OrderStatus st,
std::string const &error) {
bts_assert(!me->pending_terminate.has_value());
bts_assert(working_children_count());
me->pending_terminate = std::make_pair(st, error);
LOG(OrderMan, Info,
"AlgoOrder {} is shutting down. Waiting for {} child orders to complete "
"before terminating",
key().value(), working_children_count());
transition(Status::SHUTTING_DOWN);
}
void AlgoWrapperOrder::cleanup() {
for (auto const &s : me->book_subs) {
me->services->unsubscribe_from_book(s.second);
me->book_subs.clear();
}
for (auto &c : me->children) {
for (auto &c : me->children)
c.second.order.cancel();
me->children.clear();
}
if (me->has_timer_sub)
unsubscribe_timer();
me->pending_update.reset();
@@ -785,6 +869,16 @@ OrderKey AlgoWrapperOrder::index_and_launch_child(ChildOrder &&child, Price p,
return k;
}
size_t AlgoWrapperOrder::working_children_count() const {
auto working_children_count = 0;
for (auto &c : me->children)
if (!c.second.state.has_value() ||
c.second.state.value().status == ChildStatus::WORKING) {
working_children_count++;
}
return working_children_count;
}
bool AlgoWrapperOrder::is_working() const {
return me->status == Status::WORKING;
}
@@ -799,6 +893,8 @@ log::CharPtrWrapper AlgoWrapperOrder::state_name(Status st) {
return {"INACTIVE"};
case Status::WORKING:
return {"WORKING"};
case Status::SHUTTING_DOWN:
return {"SHUTTING_DOWN"};
}
bts_assert(false);
return {""};
@@ -40,6 +40,7 @@ class AlgoWrapperOrder : public Order,
Price price() const override;
Quantity quantity() const override;
Quantity total_fills() const override;
bool is_shutting_down() const override;
void subscribe_book(SecurityKey) override;
void unsubscribe_book(SecurityKey) override;
void subscribe_child_change(OrderKey) override;
@@ -75,10 +76,13 @@ class AlgoWrapperOrder : public Order,
void transition(Status);
void _transition(Status);
void terminate(api::orderman::OrderStatus, std::string const &error = "");
void shutdown(api::orderman::OrderStatus, std::string const &error = "");
void try_complete_shutdown();
void cleanup();
void update_child_record(api::orderman::OrderState const &, ChildRecord *);
OrderKey index_and_launch_child(ChildOrder &&, Price, Quantity, bool);
bool is_working() const ;
size_t working_children_count() const;
log::CharPtrWrapper state_name(Status) ;
ChildStatus map_child_status(api::orderman::OrderStatus) ;
bool _is_attaching(api::orderman::OrderDefinition const &) const ;
@@ -20,6 +20,13 @@ class IAlgoServices : public IBeforeLaunchAlgoServices {
// Returns the filled quantity for this algo
virtual Quantity total_fills() const = 0;
// Returns wheter the algo is in the process of shutting down (due
// to a cancel, an error, or just having completed its work). When
// algo is shutting down any requests to create, update or cancel
// child orders will be ignored.
virtual bool is_shutting_down() const = 0;
// --------------------------------------------------------------
// Event Subscriptions
// --------------------------------------------------------------
@@ -10,18 +10,8 @@ namespace orderman {
using namespace api::orderman;
enum class CoverAlgo::Status { WORKING, WAITING_FOR_HEDGES, DONE };
const char *CoverAlgo::status_name(Status st) {
switch (st) {
case Status::WORKING:
return "WORKING";
case Status::WAITING_FOR_HEDGES:
return "WAITING_FOR_HEDGES";
case Status::DONE:
return "Done";
}
return nullptr;
}
enum class CoverAlgo::Status { WORKING, WAITING_FOR_HEDGES, DONE, CANCELING };
struct CoverAlgo::PImpl {
IAlgoServices *algo;
OrderKey child_key = null_order_key();
@@ -92,8 +82,11 @@ void CoverAlgo::do_launch_attached(IAlgoServices &algo, OrderKey k,
on_child_change(k, st);
}
void CoverAlgo::on_child_change(OrderKey k, ChildState const &st) {
if (k == me->child_key) {
void CoverAlgo::on_covered_change(ChildState const &st) {
// First launch hedge if appropriate
switch (me->status) {
case Status::WORKING:
case Status::CANCELING:
if (st.total_fills.value() > me->hedged_fills.value()) {
auto hedge_qty =
Quantity(st.total_fills.value() - me->hedged_fills.value());
@@ -116,6 +109,15 @@ void CoverAlgo::on_child_change(OrderKey k, ChildState const &st) {
hedge_qty.value(), hedge_price.value());
}
algo().update_fill_state(st.total_fills, st.last_price);
break;
case Status::WAITING_FOR_HEDGES:
case Status::DONE:
break;
}
// Now analyze child to figure out wheter we should terminate
switch (me->status) {
case Status::WORKING:
switch (st.status) {
case ChildStatus::WORKING:
break;
@@ -125,36 +127,67 @@ void CoverAlgo::on_child_change(OrderKey k, ChildState const &st) {
algo().order_key().value());
algo().set_ladder_visibility(false);
me->status = Status::WAITING_FOR_HEDGES;
terminate_if_hedges_complete();
break;
case ChildStatus::CANCELED:
LOG(OrderMan, Info, "CoverAlgo {}. Child was canceled, terminating",
LOG(OrderMan, Info, "CoverAlgo {}. Child was unexpectadly canceled, terminating",
algo().order_key().value());
me->status = Status::DONE;
on_done();
algo().terminate_error("Unexpected child cancel");
break;
}
} else {
break;
case Status::CANCELING:
switch (st.status) {
case ChildStatus::WORKING:
break;
case ChildStatus::CANCELED:
if (me->cancel_on_external_hedge_cancel) {
LOG(OrderMan, Info, "CoverAlgo {}. Hedge was canceled, terminating",
algo().order_key().value());
me->status = Status::DONE;
algo().terminate_error("Unexpected child cancel");
} else
me->hedges.erase(
std::remove(me->hedges.begin(), me->hedges.end(), k),
me->hedges.end());
break;
case ChildStatus::COMPLETED:
me->hedges.erase(std::remove(me->hedges.begin(), me->hedges.end(), k),
me->hedges.end());
LOG(OrderMan, Info, "CoverAlgo {}. Shutdown complete",
algo().order_key().value());
on_done();
algo().cancel();
break;
}
break;
case Status::WAITING_FOR_HEDGES:
case Status::DONE:
break;
}
check_terminate();
}
void CoverAlgo::on_hedge_change(OrderKey k, ChildState const &st) {
// First adjust our hedge count or cancel if something went wrong
switch (st.status) {
case ChildStatus::WORKING:
break;
case ChildStatus::COMPLETED:
me->hedges.erase(std::remove(me->hedges.begin(), me->hedges.end(), k),
me->hedges.end());
break;
case ChildStatus::CANCELED:
if (me->cancel_on_external_hedge_cancel) {
LOG(OrderMan, Info, "CoverAlgo {}. Hedge was canceled, terminating",
algo().order_key().value());
on_done();
algo().terminate_error("Unexpected child cancel");
return;
} else
me->hedges.erase(std::remove(me->hedges.begin(), me->hedges.end(), k),
me->hedges.end());
break;
}
// Now if we are waiting for hedges to complete, bail
terminate_if_hedges_complete();
}
void CoverAlgo::on_child_change(OrderKey k, ChildState const &st) {
if (k == me->child_key)
on_covered_change(st);
else
on_hedge_change(k, st);
}
void CoverAlgo::do_update(api::orderman::Update const &update) {
@@ -211,7 +244,7 @@ void CoverAlgo::update_definition(
cover.cancel_on_external_hedge_cancel();
}
void CoverAlgo::check_terminate() {
void CoverAlgo::terminate_if_hedges_complete() {
LOG(OrderMan, Diag,
"CoverAlgo {} check_terminate. State: {} Hedge count: {}",
algo().order_key(), log::CharPtrWrapper{status_name(me->status)},
@@ -220,15 +253,61 @@ void CoverAlgo::check_terminate() {
if (me->status == Status::WAITING_FOR_HEDGES && me->hedges.empty()) {
LOG(OrderMan, Info, "CoverAlgo {}. Last hedge completed, terminating",
algo().order_key().value());
on_done();
algo().complete();
}
}
IAlgoOrder::OnCancelResponse CoverAlgo::on_cancel() {
switch (me->status) {
case Status::WORKING: {
algo().cancel_child(me->child_key);
LOG(OrderMan, Info,
"CoverAlgo {} received cancel. Canceling child and waiting for an "
"ack",
algo().order_key());
me->status = Status::CANCELING;
return IAlgoOrder::OnCancelResponse::IGNORE;
}
case Status::CANCELING:
return IAlgoOrder::OnCancelResponse::IGNORE;
case Status::WAITING_FOR_HEDGES:
maybe_detach_hedges();
return IAlgoOrder::OnCancelResponse::HONOR;
case Status::DONE:
bts_assert(false);
return IAlgoOrder::OnCancelResponse::HONOR;
}
bts_assert(false);
return IAlgoOrder::OnCancelResponse::HONOR;
}
void CoverAlgo::on_done()
{
me->status = Status::DONE;
maybe_detach_hedges();
}
void CoverAlgo::maybe_detach_hedges() {
if (!me->cancel_hedges_on_cancel)
for (auto &k : me->hedges)
algo().detach_child(k);
return IAlgoOrder::OnCancelResponse::HONOR;
}
const char *CoverAlgo::status_name(Status st) {
switch (st) {
case Status::WORKING:
return "WORKING";
case Status::WAITING_FOR_HEDGES:
return "WAITING_FOR_HEDGES";
case Status::DONE:
return "DONE";
case Status::CANCELING:
return "CANCELING";
}
return nullptr;
}
} // namespace orderman
@@ -30,8 +30,13 @@ private:
api::orderman::CoverOrderDefinition const &);
void init_hedge_template(IBeforeLaunchAlgoServices &,
api::orderman::CustomOrderDefinition const &);
void check_terminate();
void terminate_if_hedges_complete();
void on_hedge_change(OrderKey hedge, ChildState const&) ;
void on_covered_change(ChildState const&);
const char *status_name(Status st);
void maybe_detach_hedges();
void on_done();
};
} // namespace orderman
@@ -145,7 +145,7 @@ void IcebergAlgo::on_child_change(OrderKey k, ChildState const &st) {
on_timer();
return;
case ChildStatus::CANCELED:
algo().terminate_error("Unexpected child cancel");
algo().cancel("Unexpected child cancel");
return;
}
}
@@ -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;
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@@ -0,0 +1,39 @@
#+TITLE: Algo Wrapper State Machien
#+OPTIONS: ^:nil
#+OPTIONS: H:2
#+OPTIONS: ^:nil H:2 author:nil creator:nil timestamp:nil toc:nil
#+OPTIONS: html-postamble:nil
* State Machine
#+begin_src dot :file algo_wrapper_sm.png :cmdline -Kdot -Tpng
digraph G {
bgcolor="transparent";
edge [ fontname=Helvetica, fontsize=12 ];
start [style=invisible];
COMPLETED [style=filled] ;
start -> INACTIVE;
INACTIVE -> COMPLETED [label="qty=0|error"];
INACTIVE -> W_ATTACH [label="ok & attach"];
INACTIVE -> WORKING [label="ok & !attach"];
W_ATTACH -> COMPLETED [label=attach_fail];
W_ATTACH -> WORKING [label=attach_ok];
WORKING -> COMPLETED [label="[wchild=0]cancel|complete"];
WORKING -> COMPLETED [label=error];
WORKING -> SHUTTING_DOWN [label="[wchild>0]cancel|complete"];
SHUTTING_DOWN -> COMPLETED [label="wchild==0|error|on_detach"];
}
#+END_SRC
#+RESULTS:
[[file:algo_wrapper_sm.png]]
* Invariants
| | attach_target | exernal_status | pending_update | pending_terminate | working_children | subs |
|---------------+---------------+--------------------------------------+----------------+-------------------+------------------+------|
| INACTIVE | | | | | | 0 |
| W_ATTACH | 1 | WORKING | | 0 | 0 | 0 |
| WORKING | | WORKING | 0 | 0 | | |
| SHUTTING_DOWN | | WORKING | 0 | 1 | >0 | 0 |
| COMPLETED | | CANCELED, COMPLETED, ERROR, REJECTED | 0 | 0 | 0 | 0 |
@@ -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;
};
}}
+13 -4
View File
@@ -1,6 +1,7 @@
#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"
@@ -98,6 +99,7 @@ struct OrderServiceServer::PImpl {
publish_orders(), publish_fills(),
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) {}
@@ -114,6 +116,7 @@ struct OrderServiceServer::PImpl {
OrderServiceServer::FillsPublisher publish_fills;
api::admin::AdminService admin_service;
api::position::PositionService position_service;
api::position::MarginService margin_service;
bts::md::MDConsumer &md_consumer;
metrics::MetricsContainer &stats;
DynamicOrderRegistry const &dyn_order_registry;
@@ -240,10 +243,16 @@ OrderServiceServer::OrderServiceServer(bts::api::ServiceContext &ctx,
// 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);
});
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() {
+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/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)
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)
+1
View File
@@ -27,6 +27,7 @@ struct Proxy : public IAlgoServices {
Price price() const { return Price(0); }
Quantity quantity() const { return Quantity(0); }
Quantity total_fills() const { return Quantity(0); }
bool is_shutting_down() const {return false;}
void subscribe_book(SecurityKey){};
void unsubscribe_book(SecurityKey) {}
void subscribe_child_change(OrderKey) {}
+3 -3
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,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 = std::string("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;
};
}
}
+111 -111
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,46 +21,42 @@
#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
{
namespace position
{
using MarginMap = boost::container::flat_map<orderman::AccountName, MarginResult>;
using MarginMap = boost::container::flat_map<std::string, 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);
}
}
}
@@ -321,9 +315,8 @@ 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;
const std::vector<Position>& pos, std::function<void(const MarginResult&)> cb)
{
pugi::xml_document doc;
pugi::xml_node trades_node;
create_margin_request(portfolid_id, doc, trades_node);
@@ -331,12 +324,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 +335,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 std::string& 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 std::string& 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)
@@ -424,7 +424,7 @@ void MarginServiceCore::set_verbose_debugging(bool verb)
me->verbose = verb;
}
bool MarginServiceCore::get_last_valid_result(const orderman::AccountName& acct, MarginResult& result)
bool MarginServiceCore::get_last_valid_result(const std::string& acct, MarginResult& result)
{
auto it = me->last_valid_results.find(acct);
if(it != me->last_valid_results.end())
@@ -435,7 +435,7 @@ bool MarginServiceCore::get_last_valid_result(const orderman::AccountName& acct,
return false;
}
bool MarginServiceCore::get_last_result(const orderman::AccountName& acct, MarginResult& result)
bool MarginServiceCore::get_last_result(const std::string& acct, MarginResult& result)
{
auto it = me->last_results.find(acct);
if(it != me->last_results.end())
+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 std::string&, const std::vector<Position>&,
std::function<void(const api::position::MarginResult&)>);
void set_verbose_debugging(bool);
bool get_last_valid_result(const std::string&, api::position::MarginResult&);
bool get_last_result(const std::string&, 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 std::string&, const std::vector<Position>&,
std::function<void(const api::position::MarginResult&)>);
private:
struct PImpl;
std::unique_ptr<PImpl> me;
@@ -0,0 +1,312 @@
#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)
{
if(!config::get<bts::config::SpanMargin>().enabled())
return;
LOG(Gen, Info, "MarginServiceServer: service started");
_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);
_margin->compute_margin(acct, 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
{
std::vector<Position> pos; AccountPNL pnl;
_position->query_positions(request.account(), pos, pnl);
_margin->compute_margin(request.account(), 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(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)
{
if(!config::get<bts::config::SpanMargin>().enabled())
{
api::ResultMessage resp; resp.set_success(true);
reply(resp);
return;
}
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)
{
if(!config::get<bts::config::SpanMargin>().enabled())
{
api::ResultMessage resp; resp.set_success(true);
reply(resp);
return;
}
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)
{
if(!config::get<bts::config::SpanMargin>().enabled())
{
api::ResultMessage resp; resp.set_success(true);
reply(resp);
return;
}
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;
};
}}
+5 -10
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 account;
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 = 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;
};
}
}
@@ -174,10 +174,12 @@ void PositionServicePNLHelper::_aggregate_account_pnl(
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);
@@ -188,10 +190,12 @@ void PositionServicePNLHelper::_aggregate_account_pnl(
}
// 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;
}
@@ -84,16 +84,12 @@ me(new PImpl(context,
rxcpp::operators::subscribe<api::orderman::FillsUpdate>(
[this](auto const& update) {
for (const auto& f : update.removed())
if(_valid_account_id(*f))
me->publisher.add_or_update(_on_fill(*f,false));
me->publisher.add_or_update(_on_fill(*f,false));
for (const auto& f : update.added())
if(_valid_account_id(*f))
me->publisher.add_or_update(_on_fill(*f,true));
me->publisher.add_or_update(_on_fill(*f,true));
for (const auto& f : update.changed()) {
if(_valid_account_id(*f.previous()) && _valid_account_id(*f.current())){
me->publisher.add_or_update(_on_fill(*f.previous(),false));
me->publisher.add_or_update(_on_fill(*f.current(),true));
}
me->publisher.add_or_update(_on_fill(*f.previous(),false));
me->publisher.add_or_update(_on_fill(*f.current(),true));
}
me->publisher.flush();
_compute_pnl_and_publish();
@@ -1,11 +1,6 @@
#include "catch/catch.hpp"
#include "../MarginServiceCore.h"
#include "../PositionServiceCore.h"
#include "../Position.h"
#include "../MarginResult.h"
#include "../IMarginObserver.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include "lib/config/config.h"
@@ -26,14 +21,14 @@ namespace position
class PositionTests
{
public:
static bool query_margin_webapi(MarginServiceCore& svc, const std::vector<Position>& pos, MarginResult& res)
{
return svc.query_margin_webapi(pos, res);
}
static void set_verbose_debugging(MarginServiceCore& svc, bool verb)
{
return svc.set_verbose_debugging(verb);
}
// static bool query_margin_webapi(MarginServiceCore& svc, const std::vector<Position>& pos, MarginResult& res)
// {
// return svc.query_margin_webapi(pos, res);
// }
// static void set_verbose_debugging(MarginServiceCore& svc, bool verb)
// {
// return svc.set_verbose_debugging(verb);
// }
};
@@ -155,7 +155,7 @@ void AdminServiceServer::get_filtered_admin_data(
const api::Response<AdminData>& reply)
{
bts::api::user::TokenPayload toke;
if(bts::api::auth::decode_token(token, &toke))
bts::api::auth::decode_token(token, &toke);
{
if(toke.wheel())
reply(_state);
+1 -1
View File
@@ -1 +1 @@
set(sources ../../externals/linux/include/google/protobuf/descriptor.proto;../../externals/linux/include/google/protobuf/timestamp.proto;../lib/api/apicore.proto;../lib/api/apigateway.proto;../lib/api/apioptions.proto;../lib/api/services/AdminService.proto;../lib/api/services/AlgoDefinitionService.proto;../lib/api/services/AlgoService.proto;../lib/api/services/ApiUnitTestService.proto;../lib/api/services/AuthService.proto;../lib/api/services/ErrorReportService.proto;../lib/api/services/HelloService.proto;../lib/api/services/MBOService.proto;../lib/api/services/MarketDataAuxiliaryService.proto;../lib/api/services/MarketDataRelay.proto;../lib/api/services/OrderService.proto;../lib/api/services/OutOfProcessURLListener.proto;../lib/api/services/PersistenceService.proto;../lib/api/services/PositionService.proto;../lib/api/services/ShellDiagnosticService.proto;../lib/api/services/ShellLatencyService.proto;../lib/api/services/SyntheticSecurityService.proto;../lib/domain_model/Security.proto;../lib/md/protos/co2.proto;../lib/sparkproto/SparkProtos.proto;api/build.sh;api/node_modules/exit/test/fixtures/create-files.sh;api/node_modules/protobufjs/cli/node_modules/acorn-jsx/node_modules/acorn/bin/update_authors.sh;api/node_modules/watch/release.sh;api/quiet_build.sh;api/src/DynamicService.ts;api/src/ServiceContext.ts;api/src/bts_assert.ts;ui/src/Access.fs;ui/src/Accounts/ClipLimits.fs;ui/src/Accounts/ProductLimits.fs;ui/src/Accounts/SecurityLimits.fs;ui/src/Accounts/State.fs;ui/src/Accounts/Types.fs;ui/src/Accounts/View.fs;ui/src/App.fs;ui/src/Cmd.fs;ui/src/Controls/Button.fs;ui/src/Controls/DataGrid.fs;ui/src/Controls/Fields.fs;ui/src/Controls/Modal.fs;ui/src/Controls/ReactGrid.fs;ui/src/Controls/Table.fs;ui/src/Controls/nbsp.fs;ui/src/Global.fs;ui/src/Imports.fs;ui/src/Lens.fs;ui/src/Login/Login.fs;ui/src/Nav.fs;ui/src/State.fs;ui/src/Types.fs;ui/src/Users/State.fs;ui/src/Users/Types.fs;ui/src/Users/View.fs;ui/src/Util/EnumUtils.fs;ui/src/Util/JSMap.fs;ui/src/obj/Debug/netstandard2.0/ui.AssemblyInfo.fs)
set(sources ../../externals/linux/include/google/protobuf/descriptor.proto;../../externals/linux/include/google/protobuf/timestamp.proto;../lib/api/apicore.proto;../lib/api/apigateway.proto;../lib/api/apioptions.proto;../lib/api/services/AdminService.proto;../lib/api/services/AlgoDefinitionService.proto;../lib/api/services/AlgoService.proto;../lib/api/services/ApiUnitTestService.proto;../lib/api/services/AuthService.proto;../lib/api/services/ErrorReportService.proto;../lib/api/services/HelloService.proto;../lib/api/services/MBOService.proto;../lib/api/services/MarginService.proto;../lib/api/services/MarketDataAuxiliaryService.proto;../lib/api/services/MarketDataRelay.proto;../lib/api/services/OrderService.proto;../lib/api/services/OutOfProcessURLListener.proto;../lib/api/services/PersistenceService.proto;../lib/api/services/PositionService.proto;../lib/api/services/ShellDiagnosticService.proto;../lib/api/services/ShellLatencyService.proto;../lib/api/services/SyntheticSecurityService.proto;../lib/domain_model/Security.proto;../lib/md/protos/co2.proto;../lib/sparkproto/SparkProtos.proto;api/build.sh;api/node_modules/exit/test/fixtures/create-files.sh;api/node_modules/protobufjs/cli/node_modules/acorn-jsx/node_modules/acorn/bin/update_authors.sh;api/node_modules/psl/scripts/ci-build.sh;api/node_modules/watch/release.sh;api/quiet_build.sh;api/src/DynamicService.ts;api/src/ServiceContext.ts;api/src/bts_assert.ts;ui/src/Access.fs;ui/src/Accounts/ClipLimits.fs;ui/src/Accounts/ProductLimits.fs;ui/src/Accounts/SecurityLimits.fs;ui/src/Accounts/State.fs;ui/src/Accounts/Types.fs;ui/src/Accounts/View.fs;ui/src/App.fs;ui/src/Cmd.fs;ui/src/Controls/Button.fs;ui/src/Controls/DataGrid.fs;ui/src/Controls/Fields.fs;ui/src/Controls/Modal.fs;ui/src/Controls/ReactGrid.fs;ui/src/Controls/Table.fs;ui/src/Controls/nbsp.fs;ui/src/Global.fs;ui/src/Imports.fs;ui/src/Lens.fs;ui/src/Login/Login.fs;ui/src/Nav.fs;ui/src/State.fs;ui/src/Types.fs;ui/src/Users/State.fs;ui/src/Users/Types.fs;ui/src/Users/View.fs;ui/src/Util/EnumUtils.fs;ui/src/Util/JSMap.fs;ui/src/obj/Debug/netstandard2.0/ui.AssemblyInfo.fs)
+34 -1
View File
@@ -1,6 +1,7 @@
[<AutoOpen>]
module Access
open bts.api.admin
open bts.api.position
open bts.api
open Elmish
open bts.api.orderman
@@ -20,6 +21,12 @@ let getAccountOrdersData (admin:IAdminService) (success) (fail) =
success
(fun e -> fail e.Message)
let getMarginData (span:IMarginService) (account_name: string) (success) (fail) =
Cmd.ofPromise
span.get_excess_liquidity_report
{ account = account_name }
success
(fun e -> fail e.Message)
module Lens =
// type Setter<'a,'b> = {
@@ -106,13 +113,39 @@ module Accounts =
riskLimitEnforcement =
{ productLimitInNetTerms = true
}
accountLossLimit =
accountLossLimit =
{
enforce = false
totalLossLimit = 0u
}
}
let emptyMargin : ExcessLiquidityReport = {
account = "ERROR NO DATA"
accountId = 0u
marginResult = {
account = "ERROR NO DATA"
status = bts.api.position.MarginResult.ResultStatus.Error
portfolioId = ""
settleIndicator = ""
settleQualifier = ""
asOfTime = ""
calculationId = ""
optValue = 0.0
nonoptValue = 0.0
maintMargin = 0.0
initialMargin = 0.0
}
shouldEnforceMargin = false
leverageFactor = 0.0
cashBalance = 0.0
accountPnl = {
accountId = 0u
accountName = ""
dayPnl = 0.0
}
excessLiquidity = 0.0
}
+49 -7
View File
@@ -2,12 +2,14 @@ module Accounts.State
open Accounts.Types
open bts.api.admin
open Global
open Elmish
open Fable.PowerPack.PromiseImpl
open Fable.Import
open bts.co2
open Fable
open Fable.PowerPack
open bts.api.position
let isNewAccount (account:BTSAccount) =
account.id = 0u && account.isPresent
@@ -17,8 +19,12 @@ let currentAccountIsDefault (account:BTSAccount) = (account.id = 0u)
let init (context:ServiceContext.ServiceContext) =
let admin = AdminService.Create context ""
let mdr = MarketDataRelay.Create context ""
let span = MarginService.Create context ""
{ tab = General
admin = admin
marginService = span
marginData = emptyMargin
marginScratch = emptyMargin
adminData = None
ordersData = []
SelectedOrders = []
@@ -75,11 +81,21 @@ let applySIMUpdate (data:SIMAccountData) = function
let saveAccount (admin:IAdminService) (account:BTSAccount) (model:Model) = promise {
let! result = admin.update_account { completeData = account }
if result.isSuccess then
let! result2 = model.marginService.set_cash_factor { account = model.scratch.name; leverageFactor = model.marginScratch.leverageFactor }
let! result3 = model.marginService.set_cash_balance { account = model.scratch.name; cashBalance = model.marginScratch.cashBalance }
let! result4 = model.marginService.set_should_enforce_margin { account = model.scratch.name; shouldEnforceMargin = model.marginScratch.shouldEnforceMargin }
if (result.isSuccess && result2.success && result3.success && result4.success) then
let! data = admin.get_filtered_admin_data ()
return UpdateAdminData data
elif (not result.isSuccess) then
return (ErrorOccured (sprintf "Error saving account: %s" result.errorText))
elif (not result2.success) then
return ErrorOccured (sprintf "Error saving leverage factor: %s" result2.errorMessage)
elif (not result4.success) then
return ErrorOccured (sprintf "Error saving Enfrce Margin: %s" result4.errorMessage)
else
return ErrorOccured (sprintf "Error saving account: %s" result.errorText)
return ErrorOccured (sprintf "Error saving cash balence: %s" result3.errorMessage)
}
let createAccount (admin:IAdminService) (account:BTSAccount) (model:Model) = promise {
@@ -194,7 +210,31 @@ let update msg (model:Model) : Model * Cmd<Msg> =
| _ -> []
{model with SelectedOrders = so}, Cmd.Empty
| SelectAccount account ->
{ (clearSelection model) with previousScratch = model.scratch; scratch = account }, Cmd.Empty
{ (clearSelection model) with previousScratch = model.scratch; scratch = account; marginScratch = emptyMargin }, Cmd.ofMsg <| GetMarginData
| GetMarginData ->
{ model with ShowLoading = false}, (getMarginData model.marginService model.scratch.name UpdateMarginData (box>>ErrorOccured))
| UpdateMarginData newData ->
let cmd = Cmd.ofPromise (fun _ -> promise { do! Promise.sleep 10000 } ) () (fun _ -> GetMarginData) (fun _ -> GetMarginData)
match (model.marginScratch = emptyMargin || model.marginScratch.account <> model.scratch.name),
(compareDouble model.marginData.leverageFactor model.marginScratch.leverageFactor 10000u),
(compareDouble model.marginData.cashBalance model.marginScratch.cashBalance 10000u),
(model.marginData.shouldEnforceMargin = model.marginScratch.shouldEnforceMargin) with
| true, _, _, _ -> {model with marginData = newData; marginScratch = newData}, cmd
| false, true, true, true -> {model with marginData = newData; marginScratch = newData}, cmd
| false, false, true, true -> {model with marginData = newData; marginScratch = {model.marginScratch with cashBalance = newData.cashBalance; shouldEnforceMargin = newData.shouldEnforceMargin}}, cmd
| false, true, false, true -> {model with marginData = newData; marginScratch = {model.marginScratch with leverageFactor = newData.leverageFactor; shouldEnforceMargin = newData.shouldEnforceMargin}}, cmd
| false, false, false, true -> {model with marginData = newData; marginScratch = { model.marginScratch with shouldEnforceMargin = newData.shouldEnforceMargin}}, cmd
| false, true, true, false -> {model with marginData = newData; marginScratch = {model.marginScratch with cashBalance = newData.cashBalance; leverageFactor = newData.leverageFactor}}, cmd
| false, false, true, false -> {model with marginData = newData; marginScratch = {model.marginScratch with cashBalance = newData.cashBalance}}, cmd
| false, true, false, false -> {model with marginData = newData; marginScratch = {model.marginScratch with leverageFactor = newData.leverageFactor}}, cmd
| false, false, false, false -> {model with marginData = newData}, cmd
| SetMarginCashBalance newBalance ->
{ model with marginScratch = { model.marginScratch with cashBalance = newBalance}},Cmd.Empty
| SetMarginCashFactor newFactor ->
{ model with marginScratch = { model.marginScratch with leverageFactor = newFactor}},Cmd.Empty
| SetEnforceMargin enf ->
{ model with marginScratch = { model.marginScratch with shouldEnforceMargin = enf}},Cmd.Empty
| DeleteAccount account ->
let mdl = { model with OptionsMenuOpen = ""}
match Fable.Import.Browser.window.confirm (sprintf "Are you sure you want to delete '%s'" account.name) with
@@ -208,7 +248,7 @@ let update msg (model:Model) : Model * Cmd<Msg> =
| SaveScratch ->
match isNewAccount model.scratch with
| true -> model, Cmd.ofExistingPromise (createAccount model.admin model.scratch model ) id (box>>ErrorOccured)
| false -> model, Cmd.ofExistingPromise (saveAccount model.admin model.scratch model ) id (box>>ErrorOccured)
| false -> { model with marginScratch = model.marginData }, Cmd.ofExistingPromise (saveAccount model.admin model.scratch model ) id (box>>ErrorOccured)
| ErrorOccured msg ->
Global.alert Global.Error (string msg)
Browser.console.log msg
@@ -217,6 +257,8 @@ let update msg (model:Model) : Model * Cmd<Msg> =
match tab with
| OrdersTab ->
{ (clearSelection model) with tab = tab }, Cmd.ofMsg GetOrdersData
| MarginTab ->
{ (clearSelection model) with tab = tab }, Cmd.ofMsg GetMarginData
| _ ->
{ (clearSelection model) with tab = tab }, Cmd.Empty
| Update field ->
@@ -227,8 +269,8 @@ let update msg (model:Model) : Model * Cmd<Msg> =
{ model with scratch = { model.scratch with securityClipLimits = { limit = limits } }; SelectedClipLimits = selectedlimits }, Cmd.Empty
| AddClipLimit limit ->
let limits = match (Array.exists (fun (elm:AccountSecurityClipLimitUnit) -> elm.security.securitySymbol = "") model.scratch.securityClipLimits.limit) with
| true -> model.scratch.securityClipLimits.limit
| false -> Array.concat [ model.scratch.securityClipLimits.limit; [| limit |] ]
| true -> model.scratch.securityClipLimits.limit
| false -> Array.concat [ model.scratch.securityClipLimits.limit; [| limit |] ]
{ model with scratch = { model.scratch with securityClipLimits = { limit = limits } }}, Cmd.Empty
| ChangeMultipleClipLimits (value, selectedLimits) ->
@@ -326,7 +368,7 @@ let update msg (model:Model) : Model * Cmd<Msg> =
| RequestExpirationNames ->
model, Cmd.ofExistingPromise (requestExpNames model.mdRelay) id (box>>ErrorOccured)
| UpdateExpirationNames (data:ExpirationNameList[]) ->
{ model with mdRelayExpNameData = data }, Cmd.Empty
{ model with mdRelayExpNameData = data }, Cmd.ofMsg GetMarginData
| OpenOptionsMenu acc ->
{model with OptionsMenuOpen = acc}, Cmd.Empty
| OpenConfirmModal (title, okMessage) ->
+15
View File
@@ -5,6 +5,7 @@ open bts.api
open bts.api.orderman
open JSMap
open bts.co2
open bts.api.position
type Tab =
| General
@@ -13,6 +14,7 @@ type Tab =
| ExchangeSettingsTab
| ConnectionsTab
| OrdersTab
| MarginTab
let venues = [| Venue.GLBX; Venue.SIM |]
@@ -50,10 +52,20 @@ type Msg =
| UpdateOrdersData of OrdersSummary
| SelectOrder of bool * OrdersSummaryUnit list
| CancelOrders of OrdersSummaryUnit list
// Margin / Span
| GetMarginData
| UpdateMarginData of ExcessLiquidityReport
| SetMarginCashBalance of double
| SetMarginCashFactor of double
| SetEnforceMargin of bool
| SelectAccount of BTSAccount
| DeleteAccount of BTSAccount
| AddAccount
| SaveLeverageFactor
| SaveCashBalance
| SaveScratch
| ErrorOccured of obj
// Tabs
| SelectTab of Tab
@@ -94,6 +106,9 @@ type Msg =
type Model = {
tab : Tab
admin : IAdminService
marginService: IMarginService
marginData: ExcessLiquidityReport
marginScratch: ExcessLiquidityReport
adminData : AdminData option
ordersData : OrdersSummaryUnit list
SelectedOrders: OrdersSummaryUnit list
+69 -4
View File
@@ -11,10 +11,17 @@ open Fable
open System
open bts.co2
open Elmish.React.Common
open Fable.Core.JsInterop
open Fable.Core
let accountIsSelected (initialState:BTSAccount) (scratch:BTSAccount) = scratch <> initialState
[<Emit("$0.toLocaleString('en-US')")>]
let withCommas (n:float) : string = jsNative
[<Emit("$0.toLocaleString('en-US', {maximumFractionDigits: 2})")>]
let withCommasAndTwoDigits (n:float) : string = jsNative
let selectedAccount model =
match model.adminData with
| Some data ->
@@ -23,9 +30,13 @@ let selectedAccount model =
| None -> None
let isDirty model =
let marginDirty = model.marginData.cashBalance <> model.marginScratch.cashBalance ||
model.marginData.leverageFactor <> model.marginScratch.leverageFactor ||
model.marginData.shouldEnforceMargin <> model.marginScratch.shouldEnforceMargin
match selectedAccount model with
| Some account ->
account <> model.scratch
account <> model.scratch ||
marginDirty
| None -> false
let saveButton model dispatch =
@@ -80,6 +91,56 @@ let generalView model dispatch =
accountControls model dispatch
]
let marginView model dispatch =
let disabled = not model.marginScratch.shouldEnforceMargin
let headerRow:TableControl.row =
{
cells =
[
TableControl.Cell (div [Style [MinWidth "3em"]][str "Time"])
TableControl.Cell (str "Account")
TableControl.Cell (str "Leverage Factor")
TableControl.Cell (str "Cash Balance")
TableControl.Cell (str "Non Opt Value")
TableControl.Cell (str "Opt Value")
TableControl.Cell (str "Initial Margin")
TableControl.Cell (str "Maint. Margin")
TableControl.Cell (str "Day PnL")
TableControl.Cell (str "Excess Liquidity")
]
className = ""
header = None
}
let rws:TableControl.row =
{
cells =
[
TableControl.Cell (str model.marginData.marginResult.asOfTime.[0..9])
TableControl.Cell (str model.marginData.account)
TableControl.Cell (Fields.disabledSpan (withCommas model.marginData.leverageFactor) disabled)
TableControl.Cell (Fields.disabledSpan (withCommas model.marginData.cashBalance) disabled)
TableControl.Cell (str (withCommasAndTwoDigits model.marginData.marginResult.nonoptValue))
TableControl.Cell (str (withCommasAndTwoDigits model.marginData.marginResult.optValue))
TableControl.Cell (str (withCommasAndTwoDigits model.marginData.marginResult.initialMargin))
TableControl.Cell (str (withCommasAndTwoDigits model.marginData.marginResult.maintMargin))
TableControl.Cell (str (withCommasAndTwoDigits model.marginData.accountPnl.dayPnl))
TableControl.Cell (str (withCommasAndTwoDigits model.marginData.excessLiquidity))
]
className = ""
header = None
}
div [][
TableControl.createDataTable (Some headerRow) [rws] (span [][])
Fields.checkbox "Enforce Margin Limit" "" (model.marginScratch.shouldEnforceMargin) [] (SetEnforceMargin >> dispatch)
Fields.double "Leverage Factor:" (model.marginScratch.leverageFactor) [Disabled disabled] (double >> SetMarginCashFactor >> dispatch)
Fields.number "Cash Balance:" (int model.marginScratch.cashBalance) [Disabled disabled] (double >> SetMarginCashBalance >> dispatch)
accountControls model dispatch
]
let GetSymbolArray (model:Model) =
Array.map<bts.api.Product, string> (fun a -> a.symbol ) model.mdRelayProductData
|> Array.distinct
@@ -681,9 +742,10 @@ let securityLimitsView model dispatch =
let LimitsView model dispatch =
div [ Class "limits-container"] [
lossLimitView model dispatch
br []
lazyClipLimitsView model dispatch
lazyProductLimitsView model dispatch
lossLimitView model dispatch
//securityLimitsView model dispatch
accountControls model dispatch
@@ -784,7 +846,8 @@ let tabContent model dispatch =
| ConnectionsTab, _ -> ConnectionsView model dispatch
| CreditLimitsTab, _ -> creditLimitsView model dispatch
| ExchangeSettingsTab, _ -> exchangeSettings model dispatch
| OrdersTab, _ -> ordersView model dispatch)
| OrdersTab, _ -> ordersView model dispatch
| marginTab, _ -> marginView model dispatch)
]
let isActive account = function
@@ -918,6 +981,7 @@ let tabName = function
| ConnectionsTab -> "Connections"
| ExchangeSettingsTab -> "Exchange Settings"
| OrdersTab -> "Orders"
| MarginTab -> "Margin"
| other -> string other
let tabHeader tab currentTab dispatch =
@@ -952,6 +1016,7 @@ let root (model:Model) dispatch =
// tabHeader CreditLimits model.tab dispatch
(if (Array.length data.accounts > 0) then (tabHeader ExchangeSettingsTab model.tab dispatch) else span [][] )
(if (Array.length data.accounts > 0) then (tabHeader OrdersTab model.tab dispatch) else span [][] )
(if (Array.length data.accounts > 0) then (tabHeader MarginTab model.tab dispatch) else span [][] )
]
]
tabContent model dispatch
+8
View File
@@ -16,6 +16,11 @@ let Label (lbl:string) =
label [ ClassName "label" ]
[ str lbl ]
let disabledSpan (text:string) (disabled: bool) =
match disabled with
| true -> span [Class "disabled-label"] [str text]
| false -> span [] [str text]
let text (lbl:string) (dv:string) (attrs:IHTMLProp list) onchange =
div [ ClassName "field" ]
@@ -150,6 +155,9 @@ let password (lbl:string) (dv:string) (attrs:IHTMLProp list) onchange =
let number (lbl:string) (dv:int) (attrs:IHTMLProp list) onchange =
text lbl (string dv) attrs (int>>onchange)
let double (lbl:string) (dv:double) (attrs:IHTMLProp list) onchange =
text lbl (string dv) attrs (double>>onchange)
let enumSelect<'a> (lbl:string) (dv:'a) (values:'a[]) (attrs:IHTMLProp list) (onchange:'a->unit) =
let vals = match (Array.tryFindIndex (fun elm -> unbox elm = unbox dv) values) with
| None -> Array.concat [[| dv |]; values]
+3 -1
View File
@@ -11,6 +11,8 @@ type AlertLevel =
[<Emit("alert($1)")>]
let alert (_:AlertLevel) (_:string) = Fable.Core.Exceptions.jsNative
let compareDouble (d1:double) (d2:double) (precision: uint32) = (int (d1 * (double precision))) = (int (d2 * (double precision)))
module Reflection =
open Fable.Core.JsInterop
@@ -21,7 +23,7 @@ module Reflection =
[<Emit("Object.values($0)")>]
let values<'a> x : 'a[] = jsNative
let iterate<'a> x : (string*'a)[] =
let iterate<'a> x : (string*'a)[] =
Array.zip (keys x) (values x)
let spread<'a> (a:'a) (b:obj) : 'a = Imports.DynamicService.merge (a,b)
+2 -1
View File
@@ -70,4 +70,5 @@ let CMEMicMap:Map<string, bts.api.CMEMics> = Map.empty
.Add("XCME", bts.api.CMEMics.CME)
.Add("XNYM", bts.api.CMEMics.NYMEX)
.Add("XCBT", bts.api.CMEMics.CBT)
.Add("XCEC", bts.api.CMEMics.COMEX)
.Add("XCEC", bts.api.CMEMics.COMEX)
.Add("DME", bts.api.CMEMics.DME)
+2
View File
@@ -28,6 +28,8 @@
<Compile Include="$(API)/fable/HelloService.fable.fs" />
<Compile Include="$(API)/fable/OrderService.fable.fs" />
<Compile Include="$(API)/fable/AdminService.fable.fs" />
<Compile Include="$(API)/fable/PositionService.fable.fs" />
<Compile Include="$(API)/fable/MarginService.fable.fs" />
<Compile Include="$(API)/fable/co2.fable.fs" />
<Compile Include="$(API)/fable/MarketDataRelay.fable.fs" />
<!-- Global to the app -->
+15 -1
View File
@@ -15,7 +15,7 @@ This will test our ability to generate and enforce keycodes.
You should see keycode details printed to screen
4) Now test concurrent logins. Start a sparkd.
4) Now test concurrent logins. Start a sparkd. Make sure bts_2 and bts_3 have market data permission
a) Start a spark -multiple login as bts and go to where you would see some market data
b) Start another spark -multiple but log in as bts_2 and display some data
c) Start a third spark -multiple, log in as bts_2 and display some data.
@@ -37,5 +37,19 @@ You should see keycode details printed to screen
d) remove the expired keycode you just generated by:
rm ~/sparkdata/conf/keycode.conf
e) restart sparkd, you should see data as normal in bts_2
6) Test admin vs trading logins
Note that an admin login is one that does not have trading permission in any account
AND does not have the ability to see any market data
a) start sparkd again. make sure bts_2 and bts_3 have market data and trading permissions in webui
b) Start a spark -multiple login as bts_2 and go to where you would see some market data. You should see data
c) Start a webui and log in as bts_3, you should see data
d) close your shell and log out of web ui, now log in bts_3 into the webui and log in bts_2 in a shell (
this is the reverse of the previous order), you should see a message in the shell about keycodes, but it will let you log in, just not see market data
e) from the webui, de-permission bts_3 from trading AND market data,
f) exit shell, log out of webui and kill sparkd, restart sparkd
g)now try d again-- you should not get the same shell error
+29
View File
@@ -0,0 +1,29 @@
Test Author: Kevin
If it breaks: Kevin
This will test our ability to compute and enforce span margin at CME
1) Make sure span.conf looks like:
SpanMargin {
value {
enabled: true
login: 'api_bts1'
password: 'Summer123'
url: 'https://cmecorenr.cmegroup.com/MarginServiceApi/1.4/'
}
}
2) Fire up a shell and trade 1 ES into ACC A
3) Open a webui, look at Margin Tab in ACC A
You should see a value of around 6300 in maintenance margin after a minute or two
4) If not already zero, set your cash balance to zero.
5) Try to trade an ES in ACC A, you should be blocked by a limit breach
6) Increase your cash balance to 10,000 and set leverage factor to 1.0
7) Try to trade again, you should be able to.