Compare commits
21
Commits
2.16.2
..
2.17.0_rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
888b4e22c0 | ||
|
|
534ce5a8ec | ||
|
|
c2b9ac57dc | ||
|
|
2783361325 | ||
|
|
46d35131d7 | ||
|
|
3b95154c04 | ||
|
|
1a6b918853 | ||
|
|
bc22605a25 | ||
|
|
f3d28add1c | ||
|
|
63d260a6f8 | ||
|
|
9b4a63b4a8 | ||
|
|
a502c365e5 | ||
|
|
fdacd8b543 | ||
|
|
3d1de9a0c7 | ||
|
|
b2b3fa5949 | ||
|
|
86ecf69820 | ||
|
|
8f537a8fda | ||
|
|
01b9ff6b47 | ||
|
|
2609663d3e | ||
|
|
fd89241611 | ||
|
|
a69118c5d3 |
No files matched your search
+1
-1
@@ -55,7 +55,7 @@ else ()
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.13.0.0 ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0 ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
|
||||
COMMENT "Creating server runtime layout"
|
||||
|
||||
@@ -1 +1,731 @@
|
||||
+++
title = "Release notes"
weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.16.2
### User Visible Change
- Synthetic creation dialog has a "fractional" checkbox next to the tick settings. Users may uncheck this box to enter integer tick sizes, so the synthetic security's prices will be displayed without decimals, or fractional units.
## 2.16.1
### Bug Fixes
- OrderBook's algo state parameters (in the panel on the right side) are sorted alphabetically
## 2.16.0
### Bug Fixes
- Solved an issue that could result in incorrect ladder levels
- Solved an issue that could lead to incorrect routing of orders in the event of certain MSGW changes
### API Changes
- Add C# client side API samples for order entry, position monitoring and market data observation
## 2.15.10
### Bug Fixes
- Solved an issue that could cause the user interface to fail when playing some alert sounds
### API Changes
- Added settlement and position information to the API
- Added the ability for user algos to report custom state to the user interface
- Added the ability for algos to discover all active securities
## 2.15.3
### Bug Fixes
- Fixed a bug where some CME currency spreads exhibited incorrect tick values
## 2.15.2
### User Visible Changes
- Excel RTD plugin can now receive ICE market data
## 2.15.1
### Bug Fixes
- Fixed a FIX log parsing issue that resulted in exchange gateway logon problems
## 2.15.0
### User Visible Changes
- Added ability to hide Ladder status messages via Ladder Settings -> Left Panel tab -> Show/Hide groupbox -> Status Message
- Synthetic cloning now allows for any parameter to be changed, including tick size and leg contracts
- Added ability to import synthetics from a CSV file; see our manual for further details on this process
- Reduced Iceberg reloading latency
### Bug Fixes
- Fixed a bug where highlighted fills could lose their highlight status
- Fixed a bug where the (Mini) OrderBook PIQ could be displaying an old value
## 2.14.3
### Bug Fixes
- Fixed a bug regarding ICE logon messaging
## 2.14.2
### Bug Fixes
- Fixed a bug regarding ICE messaging
## 2.14.1
### Bug Fixes
- Fixed a bug regarding ICE logon authentication
## 2.14.0
### User Visible Changes
- All ladders can now be recentered by (shift or alt) + double clicking over the Price column in a ladder
- Added a Max Order Quantity setting to the Safety Checks tab on the Ladder settings. This setting allows for a user-defined maximum order quantity.
- Added a Price Display Format setting to the Market Price and Size Display tab on the Ladder settings. This setting allows for price levels in the ladder to be displayed without a decimal separator.
- Synthetic instrument creation with 3 legs no longer defaults to butterfly quantites
### Bug Fixes
- Fixed an issue where market transition were not observed by IAlgoOrder
- Fixed a bug in error reporting
- Fixed a bug in the PnL window where specifying a certain grouping order would lead to a UI failure
## 2.13.0
### User Visible Changes
- Added the name of the spreader to the parent algo badge (for spreader child orders), in the fills, orderbook and floating orderbook windows
- Added support for sounds for new orders
- Added a new quoting throttle parameter for the spreader, that throttles quotes that are moving toward the inside market but are still a configurable number of ticks away
- Enabled the ladder to remember the left panel scroll position across front end restarts
### Bug Fixes
- Fixed the local fills window so that it does not show the admin fills
- Removed the deleted accounts from the ladder accounts dropdown
- Fixed the orderbook price for iceberg of spreaders
## 2.12.0
### User Visible Changes
- Added support for ICE market data and order execution
### Internal Changes
- Lots of permission / admin changes to facilitate ICE
## 2.11.0
### User Visible Changes
- Fixed fills form time filter to also apply to SOD fills
- Addressed an issue that could prevent the web Admin UI from displaying over slow HTTP connections
- Removed the option to enter manual spread fills
- Fixed an issue that could cause erroneous fill account attribution in the event of a crash
### Internal Changes
- Added support for tag 1031. Currently defaults to Y, can it be override through config.
- To facilitate support, allowed the bts user to view and edit algos from all other users
- Improved system tests
- Improved log messages in UI to include dispatcher thread name
- Added support for Windows core dumps in the UI
## 2.10.0
### User Visible Changes
- Fills now include information about the algo that originated the fill
- A new, more powerful PnL form is now available
### API Changes
- Added security state information (pre open, halted, normal, closed, etc)
### Bug Fixes
- Brought back the "select all" check box in the mini order book
- Fixed an issue in the market data subsyLine truncated
|
||||
+++
|
||||
title = "Release notes"
|
||||
weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.16.2
|
||||
### User Visible Change
|
||||
- Synthetic creation dialog has a "fractional" checkbox next to the tick settings. Users may uncheck this box to enter integer tick sizes, so the synthetic security's prices will be displayed without decimals, or fractional units.
|
||||
## 2.16.1
|
||||
### Bug Fixes
|
||||
- OrderBook's algo state parameters (in the panel on the right side) are sorted alphabetically
|
||||
## 2.16.0
|
||||
### Bug Fixes
|
||||
- Solved an issue that could result in incorrect ladder levels
|
||||
- Solved an issue that could lead to incorrect routing of orders in the event of certain MSGW changes
|
||||
### API Changes
|
||||
- Add C# client side API samples for order entry, position monitoring and market data observation
|
||||
|
||||
## 2.15.10
|
||||
### Bug Fixes
|
||||
- Solved an issue that could cause the user interface to fail when playing some alert sounds
|
||||
### API Changes
|
||||
- Added settlement and position information to the API
|
||||
- Added the ability for user algos to report custom state to the user interface
|
||||
- Added the ability for algos to discover all active securities
|
||||
## 2.15.3
|
||||
### Bug Fixes
|
||||
- Fixed a bug where some CME currency spreads exhibited incorrect tick values
|
||||
## 2.15.2
|
||||
### User Visible Changes
|
||||
- Excel RTD plugin can now receive ICE market data
|
||||
## 2.15.1
|
||||
### Bug Fixes
|
||||
- Fixed a FIX log parsing issue that resulted in exchange gateway logon problems
|
||||
## 2.15.0
|
||||
### User Visible Changes
|
||||
- Added ability to hide Ladder status messages via Ladder Settings -> Left Panel tab -> Show/Hide groupbox -> Status Message
|
||||
- Synthetic cloning now allows for any parameter to be changed, including tick size and leg contracts
|
||||
- Added ability to import synthetics from a CSV file; see our manual for further details on this process
|
||||
- Reduced Iceberg reloading latency
|
||||
### Bug Fixes
|
||||
- Fixed a bug where highlighted fills could lose their highlight status
|
||||
- Fixed a bug where the (Mini) OrderBook PIQ could be displaying an old value
|
||||
|
||||
## 2.14.3
|
||||
### Bug Fixes
|
||||
- Fixed a bug regarding ICE logon messaging
|
||||
|
||||
## 2.14.2
|
||||
### Bug Fixes
|
||||
- Fixed a bug regarding ICE messaging
|
||||
|
||||
## 2.14.1
|
||||
### Bug Fixes
|
||||
- Fixed a bug regarding ICE logon authentication
|
||||
|
||||
## 2.14.0
|
||||
### User Visible Changes
|
||||
- All ladders can now be recentered by (shift or alt) + double clicking over the Price column in a ladder
|
||||
- Added a Max Order Quantity setting to the Safety Checks tab on the Ladder settings. This setting allows for a user-defined maximum order quantity.
|
||||
- Added a Price Display Format setting to the Market Price and Size Display tab on the Ladder settings. This setting allows for price levels in the ladder to be displayed without a decimal separator.
|
||||
- Synthetic instrument creation with 3 legs no longer defaults to butterfly quantites
|
||||
### Bug Fixes
|
||||
- Fixed an issue where market transition were not observed by IAlgoOrder
|
||||
- Fixed a bug in error reporting
|
||||
- Fixed a bug in the PnL window where specifying a certain grouping order would lead to a UI failure
|
||||
|
||||
## 2.13.0
|
||||
### User Visible Changes
|
||||
- Added the name of the spreader to the parent algo badge (for spreader child orders), in the fills, orderbook and floating orderbook windows
|
||||
- Added support for sounds for new orders
|
||||
- Added a new quoting throttle parameter for the spreader, that throttles quotes that are moving toward the inside market but are still a configurable number of ticks away
|
||||
- Enabled the ladder to remember the left panel scroll position across front end restarts
|
||||
### Bug Fixes
|
||||
- Fixed the local fills window so that it does not show the admin fills
|
||||
- Removed the deleted accounts from the ladder accounts dropdown
|
||||
- Fixed the orderbook price for iceberg of spreaders
|
||||
|
||||
## 2.12.0
|
||||
### User Visible Changes
|
||||
- Added support for ICE market data and order execution
|
||||
### Internal Changes
|
||||
- Lots of permission / admin changes to facilitate ICE
|
||||
|
||||
## 2.11.0
|
||||
### User Visible Changes
|
||||
- Fixed fills form time filter to also apply to SOD fills
|
||||
- Addressed an issue that could prevent the web Admin UI from displaying over slow HTTP connections
|
||||
- Removed the option to enter manual spread fills
|
||||
- Fixed an issue that could cause erroneous fill account attribution in the event of a crash
|
||||
### Internal Changes
|
||||
- Added support for tag 1031. Currently defaults to Y, can it be override through config.
|
||||
- To facilitate support, allowed the bts user to view and edit algos from all other users
|
||||
- Improved system tests
|
||||
- Improved log messages in UI to include dispatcher thread name
|
||||
- Added support for Windows core dumps in the UI
|
||||
|
||||
## 2.10.0
|
||||
### User Visible Changes
|
||||
- Fills now include information about the algo that originated the fill
|
||||
- A new, more powerful PnL form is now available
|
||||
### API Changes
|
||||
- Added security state information (pre open, halted, normal, closed, etc)
|
||||
### Bug Fixes
|
||||
- Brought back the "select all" check box in the mini order book
|
||||
- Fixed an issue in the market data subsystem that on rare occasions could result in a startup crash
|
||||
### Internal Changes
|
||||
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
|
||||
## 2.9.3
|
||||
### Internal Changes
|
||||
- Fixed system test scripts
|
||||
## 2.9.2
|
||||
### Bug Fixes
|
||||
- Fixed an issue with the exchange simulator that could lead to crossed markets
|
||||
## 2.9.1
|
||||
### Bug Fixes
|
||||
- Fixed an issue with trade sound alerts, where in some cases the use of a "In Between" condition could lead to a UI failure.
|
||||
- Fixed an issue with the display of the new raw position column in the market view window.
|
||||
- Fixed the popup label that describes the raw position information in the ladder
|
||||
### Internal Changes
|
||||
- Avoided shipping production configuration defaults that lead to trying to download a position file for the development sftp server.
|
||||
### Api
|
||||
- C# API sample now uses synchronous calls.
|
||||
|
||||
## 2.9.0
|
||||
### User Visible Changes
|
||||
- Improved Workspace management User Interface
|
||||
- Added the ability to change grid background colors
|
||||
- The Spark UI Window group now has a title in the Windows task bar
|
||||
- Added sound alerts for trade events, see Launcher -> Settings -> Alerts
|
||||
- Allowed composite algos that are attached to existing orders to identify fills that were already present prior to attachment
|
||||
- Added import facilities for GMI position files
|
||||
- Added the ability for risk managers to enter manual fills
|
||||
- Added "raw" P&L columns, that is columns that *do not* include spread legs in their accounting
|
||||
### Bug Fixes
|
||||
- Fixed the settings action menu (Load/Save/Reset to Factory)
|
||||
- Fixed the method used to determine if a security is expired to take into account the start of day time
|
||||
### API Changes
|
||||
- Fixed an issue in the Client SDK that could lead to occasional failures to connect to the spark server as well as excessive memory usage.
|
||||
- Added the ability for algo orders to distinguish fills existing prior to launch
|
||||
### Internal Changes
|
||||
- Changed how we packaged certain third-party components
|
||||
- Added logging of entitlement status
|
||||
- Tagged outgoing orders with sequence number of marketdata or FIX event that triggered them
|
||||
## 2.8.3
|
||||
### Bug Fixes
|
||||
- Fixed a bug that could cause an OrderBook's Security column filter to be blank
|
||||
- Fixed a bug that could cause a UI failure when clearing a filter selection on a filtering widget
|
||||
## 2.8.2
|
||||
### Bug Fixes
|
||||
- Added proper display factors for SOM spreads
|
||||
- Fixed a bug that could cause a UI failure when trying to change settings in a synthetic ladder
|
||||
## 2.8.1
|
||||
### Bug fixes
|
||||
- Fixed an error in the UI grids filtering widget
|
||||
## 2.8.0
|
||||
### User Visible Changes
|
||||
- Added ProRata and Scratch algos
|
||||
- Ladder badge now grows/shrinks to fill the containing cell
|
||||
- Attached algo parameters are now displayed in the update panel
|
||||
- Fixed a bug where the update panel would show '-' values when only
|
||||
1 order was selected
|
||||
- Fixed a bug that cause cause a failure on startup if the user
|
||||
loaded a workspace with a Ladder that had a dynamic order type
|
||||
selected by default
|
||||
- Increased width of login panel to better show login error messages
|
||||
- Added optional 'Maker/Taker' pricing to Spreader algo
|
||||
- Algo buttons in ladder can now be made to deactivate after launching an algo or remain 'sticky'
|
||||
### Internal changes
|
||||
- Made it easier to generate simple UI's for new order types
|
||||
### API Changes
|
||||
- Dynamic orders can now use QuantityMeasure parameter types
|
||||
|
||||
## 2.7.2
|
||||
### User Visible Changes
|
||||
- Changed the login dialog to avoid leaking workspace names from other users.
|
||||
- Added new Splitter algo. From the manual: The splitter is useful
|
||||
for pro-rata markets where you want to vary your exposure
|
||||
independent of the order quantity that you want to fill. The
|
||||
splitter will complete when the number of fills reaches or exceeds
|
||||
the order quantity but it will put exposed_qty total size on the
|
||||
market. Splitter favors order size over queue position.
|
||||
### Internal Changes
|
||||
- Made spark more resilient to lack of connectivity to the CME web site for downloading of templates.
|
||||
|
||||
## 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
|
||||
- Made algos, synthetics and user names private so that multiple users can share a single system privately.
|
||||
- Allowed the mini order book defaults for column widths to be changed from the mini order book
|
||||
- Allowed the mini order book to shrink to the minimum size that will be needed to display the selected columns
|
||||
- Improved accuracy of the directional arrow when displaying last trades in the volumn column
|
||||
## 2.4.0
|
||||
### User Visible Settings
|
||||
- In the ladder, changed the last trade column to accumulate successive trades at the same price
|
||||
- Fixed a bug in the order book where the right side ladder would display the top leve order's security rather then current row's
|
||||
- Added OK, Cancel buttons to the security picker for ladder profiles or sounds
|
||||
- Added ladder settings for the mini order book
|
||||
- Added a safety check to guard against unintentional market crosses
|
||||
- Improved the Queue Holder mode in the spreader:
|
||||
- Added a Show Quantity, allowing the spreader to keep queue position of the trailing orders as Fills (of size Show Qty) happen
|
||||
- Improved spreader performance when in Queue Holder mode
|
||||
### Internal changes
|
||||
- Suppressed order callbacks for completed/canceled orders. This should eliminate some bogus error logs by spreader orders.
|
||||
- Addressed a potential concurrency issues with service initialization.
|
||||
|
||||
|
||||
## 2.3.0
|
||||
### User Visible Changes
|
||||
- Introduced multiple settings profiles for the ladder
|
||||
- This allows multiple standard configuration for the ladder, optionally with security specific variations
|
||||
- Made Cover order more independent from its hedging chidren:
|
||||
- Cover continues to operate even if an hedging child is canceled
|
||||
- When a cover is canceled it will detach from any running hedging children, instead of canceling them
|
||||
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
|
||||
- Changed manual fills to appear in day PnL rather than in the SOD records
|
||||
- Add buy/sell coloring to fill rows in fills window
|
||||
- Streamlined the mini order book:
|
||||
- Display of the mini order book popup is quicker
|
||||
- Only exchange orders are displayed
|
||||
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
|
||||
- We can now select multiple rows to attach, detach, update or cancel multiple orders in a batch
|
||||
- A checkbox column has been added to make clearer which rows are selected
|
||||
### API Changes
|
||||
- Enhanced the API to allow algos to implement custom cancel handling
|
||||
- Allowed algos to control their visibility in the ladder through the API
|
||||
### Internal Changes
|
||||
- Added system requirements to Spark documentation
|
||||
- Implemented keycodes
|
||||
- Temporarily rejected stop and stop limit orders, pending bug fixes
|
||||
|
||||
## 2.2.0
|
||||
### User Visible Changes
|
||||
- Made our orders easier to spot in MBO display
|
||||
- Changed middle-click on center column to recenter ladder and introduced Alt middle-click on center to bring up the MBO popup
|
||||
- Fixed Net/Absolute radio button in risk admin UI
|
||||
- Fixed double counting of spread legs in limits
|
||||
- Removed expired fills from the system
|
||||
- Improved handling of orders with undiscovered securities
|
||||
- Added feedback to the ladder when the user attempts to middle click on bid/ask columns using an order that does not have parameters
|
||||
- Fixed display of algo studio algo types in order book
|
||||
- Hid net change setting in synthetic ladders
|
||||
- Fixed a number of problems with filtering on the Fills form
|
||||
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
|
||||
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
|
||||
- Added security symbol column to fills window
|
||||
- Re-organized ladder settings dialog to make it clearer and easier to use
|
||||
|
||||
## 2.1.4
|
||||
### User Visible Changes
|
||||
- Fixed handling of cancel button in security picker
|
||||
|
||||
## 2.1.3
|
||||
### User Visible Changes
|
||||
- Fixed a simulator bug in the handling of 0 sized orders
|
||||
|
||||
## 2.2.1
|
||||
### User Visible Changes
|
||||
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
|
||||
|
||||
## 2.1.1
|
||||
### User Visible Changes
|
||||
- Added PIQ to GLBX Iceberg orders
|
||||
- Corrected handling of stop limit orders
|
||||
|
||||
## 2.1.0
|
||||
### User Visible Changes
|
||||
- Added support for the GLBX Iceberg order type
|
||||
- Allowed quick algo param edits by middle-clicking on either the bid/ask columns or a "favorite" button
|
||||
- Added support for manual fills, either entered manually or imported from file. Manual fills are used only for P&L purposes, they have no impact on limits
|
||||
- Allowed the LTQ and VAP values to be overlayed on the same ladder column
|
||||
- Added day position information to the ladder
|
||||
- Added PIQ to the mini order book
|
||||
- Fixed a rare bug that could lead to server failure when requesting market data
|
||||
- Fixed a bug that could cause incorrect price displays for certain types of synthetic securities
|
||||
|
||||
## 2.0.5
|
||||
### User Visible Changes
|
||||
- Fixed a bug in the handling of rejected QHO orders
|
||||
- Implemented stricter position limit enforcement
|
||||
- Fixed a bug that could cause server failure when trying to attach to already attached orders
|
||||
|
||||
## 2.0.4
|
||||
### User Visible Changes
|
||||
- Fixed performance regression in order manager
|
||||
|
||||
### Internal changes
|
||||
- Improved market data diagnostics for bad configurations
|
||||
- Changed market data default buffer size
|
||||
|
||||
## 2.0.3
|
||||
### Internal changes
|
||||
- Improved backwards compatibility in UI settings
|
||||
|
||||
|
||||
## 2.0.2
|
||||
### User Visible Changes
|
||||
- Middle click the ladder price column to show the orders for that price level
|
||||
- Middle click the bid or ask column in the ladder no longer sends an order
|
||||
|
||||
### Internal changes
|
||||
- Improved market by order performance under heavy load
|
||||
- Fixed a concurrency bug in SIM
|
||||
|
||||
## 2.0.0
|
||||
### User Visible Changes
|
||||
- Support for attaching and detaching algos to existing orders
|
||||
- Support for display and update of all parameters in running algos
|
||||
- Streamlining of order badge decorations in ladder
|
||||
- New order search/filtering algorithm for order book
|
||||
- Simpler representation of complex algo chains in order book and mini order book
|
||||
- Visual and usability improvements to mini order book
|
||||
- Streamlining of algo parameter dialog in ladder
|
||||
- New spreader queue holder behavior
|
||||
- Visual improvements to the order book
|
||||
- New server side API
|
||||
- Improved admin UI performance
|
||||
- Optimized PIQ rendering
|
||||
### Internal changes
|
||||
- Exchange simulator no longer requires daily sequence number reset
|
||||
- Improved MD resilience to corrupted shared memory
|
||||
- Native orders can now buffer updates/cancels when disconnected or pending first acknowledgment
|
||||
- Improved settings manager tool output
|
||||
- Added diagnostics in the event MD memory corruption is detected
|
||||
- Added systems tests for join,lean, iceberg and spreader orders
|
||||
- Made exchange order recovery more resilient in the presence of database loss
|
||||
|
||||
## 1.6.2
|
||||
### User Visible Changes
|
||||
### Exchange simulator
|
||||
- Exchange simulator now now produce position in queue data
|
||||
|
||||
## 1.6.0
|
||||
### User Visible Changes
|
||||
#### New Features
|
||||
- You can now view your order's position in queue in the ladder
|
||||
#### Bug Fixes
|
||||
- Fix a bug in the order popup in the ladder which could lead to a crash.
|
||||
|
||||
## 1.5.9
|
||||
### User Visible Changes
|
||||
- Fix for stunnel connections on Windows 7
|
||||
|
||||
## 1.5.8
|
||||
### User Visible Changes
|
||||
- Fixed a bug where it was possible to crash the UI by entering an order through the order ticket if Enter was used to dismiss the dialog.
|
||||
- Fixed a spreader reporting bug which could cause the server to crash if an algo was used to control certain types of synthetics
|
||||
- Improved user message when the UI fails to login
|
||||
- Integrated stunnel into UI to make VPN setup unnecessary in unsecured environments.
|
||||
## 1.5.7
|
||||
### User Visible Changes
|
||||
- New videos have been added to the manual describing the [Algo Studio](./bts_spark/Trading/_index.md#algo-studio) and [Spreader](./bts_spark/Spreader/_index.md)
|
||||
- Fixed a bug that occasionally would cause windows to be position outside the visible screen on startup
|
||||
- Fixed a bug in the order book form that would occasionally cause the Recreate Order option to be unavailable
|
||||
- Added a confirmation step to Cancel All, Cancel Buys and Cancel Sells commands
|
||||
- Fixed Aggressor coloring and ladder high/low coloring in the SIM execution gateway
|
||||
- Added initial support for ICE market data
|
||||
- In an effort to streamline the PnL report we've made the following changes:
|
||||
- The PnL report will by default filter for Futures contracts only
|
||||
- If you wish to see Spread contracts again you may change Security Type column filter
|
||||
- The PnL report will now only show rows in which there is a position (net and/or day)
|
||||
- The Exchange, Start PnL, Total Net Pos, and Total Net PnL columns are now hidden by default
|
||||
- These columns can be restored by toggling their visibility within the PnL's Settings form
|
||||
- A new Traded Today column has been added to the PnL
|
||||
- Filtering on this column will allow a user to restrict the report to only show today's position
|
||||
### Internal changes
|
||||
- Improved organization of the shell notification architecture
|
||||
- Added logging to help troubleshoot shell exit hang
|
||||
|
||||
## 1.5.6
|
||||
### User Visible Changes
|
||||
- Fixed a bug that could cause limits to be erroneously enforced
|
||||
- Improved algo documentation
|
||||
- Changed how the spreader uses net change
|
||||
- Simplified trigger algo user interface
|
||||
### Internal changes
|
||||
- Defaulted to not recording sim market data
|
||||
- Improved documentation build
|
||||
- Added additional performance tracing
|
||||
|
||||
## 1.5.5
|
||||
- Fixed Market View 'stale working orders' bug
|
||||
|
||||
## 1.5.4
|
||||
- Avoided logging when warming up cache
|
||||
|
||||
## 1.5.3
|
||||
- Fixed sounds startup issue
|
||||
|
||||
## 1.5.2
|
||||
- Added better logging to AlgoDefinitionService
|
||||
|
||||
## 1.5.1
|
||||
- Fixed missing securities in the Fills and P&L windows.
|
||||
|
||||
## 1.5.0
|
||||
### User Visible Changes
|
||||
#### New Features
|
||||
- This release introduces a major new feature, the Algo Studio, a
|
||||
set of tools allowing traders to create their own algos by
|
||||
composing existing algos to express unique strategies.
|
||||
#### User Interface Improvements
|
||||
- Removed security key field from most grids
|
||||
- Removed trade key field from time and sales grid
|
||||
- Fixed the "Since Open" time filter to use the exchange open time rather than 8:30 AM
|
||||
- Fixed the floating order book so that rows are no longer
|
||||
highlighted after an order is modified
|
||||
- Relocated quantity and price columns in floating order book immediately after the cancel column; users now can move the mouse a much shorter distance when modifying their orders
|
||||
- The escape key now closes the floating order book
|
||||
#### Bug Fixes
|
||||
- Fixed the "Since Open" time filter to use the exchange open time rather than 8:30 AM
|
||||
- Fixed a bug in the order book where sometimes parent orders would not update their last message column.
|
||||
|
||||
#### Algos
|
||||
- Added OCO Algo
|
||||
- An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill. Composing this order with Cover simulates scalping functionality
|
||||
#### SDK
|
||||
- Improved SDK documentation and usability
|
||||
- Dynamic orders are now properly identified in the order book
|
||||
#### Documentation
|
||||
- Information on the Spark API has been added to the Spark manual
|
||||
- The documentation has been updated and reorganized to make information easier to find
|
||||
### Internal Changes
|
||||
- Improved robustness of limit system test
|
||||
- Fixed a bug in handling of built-in bts to user that could lead to failure to login as bts in old systems
|
||||
- Improved market data subsystem to support security definition updates
|
||||
- Fixed a startup race in market data that could lead to data corruption on startup
|
||||
- Increased default order price precision to 8 digis as required by some CME products
|
||||
|
||||
## 1.4.11
|
||||
### New Features
|
||||
- Floating Order Book
|
||||
- The floating order book provides a quick view of orders occupying a price level in the ladder
|
||||
- Launch the Floating Order Book by middle-clicking a ladder badge
|
||||
- Cancel orders by clicking on the red X in the Cancel column
|
||||
- Quantity and Price modification
|
||||
- Clicking on a row will select it, providing the option for changing your price and quantity
|
||||
- Enter in a new value or use the up/down buttons to set a desired value
|
||||
- Press the green checkbox to finalize your change and update the order
|
||||
### SDK
|
||||
- Improved SDK documentation and usability
|
||||
### User Interface Improvements
|
||||
- The following spreader quoting parameters have been deprecated:
|
||||
- Hidden parameter
|
||||
- Price Type setting of the Conditional parameter
|
||||
### Bug Fixes
|
||||
- Fixed a limit enforcement problem that could lead to 0 sized orders being sent to the exchange
|
||||
|
||||
## 1.4.10
|
||||
### Bug Fixes
|
||||
- Removed duplicate entries in performance log
|
||||
|
||||
## 1.4.9
|
||||
### SDK
|
||||
- Added ticker events to the order API
|
||||
### Performance Improvements
|
||||
- Significantly improved hedging speed
|
||||
### Bug Fixes
|
||||
- Fixed a visual oddity in the order book UI when using High DPI displays
|
||||
|
||||
## 1.4.8
|
||||
### Marketdata
|
||||
- Additional trade details for CME ticker
|
||||
|
||||
## 1.4.7
|
||||
### Bug Fixes
|
||||
- Corrected FIX tag 1028, ManualOrderIndicator, when orders are triggered by algos
|
||||
|
||||
## 1.4.6
|
||||
### Ladder Usability Improvements
|
||||
- The ladder can now be zoomed in/out according to a user's preference
|
||||
- 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
|
||||
|
||||
- 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
|
||||
### Bug Fixes
|
||||
- Fixed a UI crash in some cases when attempting to sort the order book by time
|
||||
|
||||
## 1.4.5
|
||||
- Fixed entitlement validation for ICS spreads
|
||||
- Tuned performance logging
|
||||
|
||||
## 1.4.4
|
||||
|
||||
### Performance Improvements
|
||||
- Reduced performance logging activity
|
||||
|
||||
## 1.4.3
|
||||
|
||||
### Ladder Usability Improvements
|
||||
- Controls found in Left Pane of the ladder can now be hidden via the checkboxes found under Ladder Form -> Settings button -> Left Pane tab
|
||||
- The Left Pane is now scrollable with the mouse wheel when there isn't enough vertical space to accomodate the entire form
|
||||
- The Left Pane can now grown and shrunk horizontally via the splitter between the left pane and the main ladder area
|
||||
### Performance Improvements
|
||||
- Requoting and hedging latency has been reduced by up to 30%
|
||||
### Software Development Kit (SDK) Improvements
|
||||
- The API now enforces authentication
|
||||
### Bug Fixes
|
||||
- The Market View form now honors filters when new securities are added
|
||||
|
||||
## 1.4.2
|
||||
|
||||
### User Interface Improvements
|
||||
- When creating a synthetic from the Market View window, securities are now added to the Create Synthetic dialog in the same sort order as they appear in grid
|
||||
### Bug Fixes
|
||||
- Shell no longer crashes when removing a security from the synthetics window
|
||||
- It is now possible to create a new synthetic using the same name as a previously deleted synthetic
|
||||
### SDK Improvements
|
||||
- Improved Client and Server SDK clarity and documentation
|
||||
### Performance Improvements
|
||||
- Synthetic calculation speed was improved by 400%.
|
||||
### Risk / Entitlement System
|
||||
- Risk Admin UI
|
||||
- Spark users now have separate Trading, RiskAdmin, and/or MDAdmin permissions
|
||||
- Spark users can now only add other users or accounts if permissioned to do so.
|
||||
- Market Data Entitlement
|
||||
- Automated market data reporting
|
||||
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Tabs
|
||||
- Tabs now shrink and expand to make better use of horizontal space
|
||||
### Bug Fixes
|
||||
- Fixed a bug that could cause excessive CPU usage when a connection broke
|
||||
- Fixed a layout bug in hedging/requoting performance reports
|
||||
- Fixed account name mismatching bug for old fills
|
||||
### Absolute Position limits
|
||||
- Positions can now be netted or absolute for purposes of limit enforcing
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Speed Enhancements
|
||||
- Improved requoting and hedging performance
|
||||
- Improved order book form performance
|
||||
### Server Side SDK
|
||||
- Added a beta version of the server side SDK
|
||||
### Front End
|
||||
- Added status bars to all UI grids
|
||||
### Reporting
|
||||
- Added reporting of hedging and requoting performance
|
||||
|
||||
## 1.3.8
|
||||
|
||||
### Speed Enhancements
|
||||
- Further speed enhancements
|
||||
### Conditional (new quoting parameter)
|
||||
- Reduces re-quoting updates by only quoting if N ticks away from the opposite inside market.
|
||||
- Quotes are canceled when it gets further away.
|
||||
- "Price Type" dictates if this logic applies to the quoting market or to the synthetic spread market.
|
||||
|
||||
## 1.3.7
|
||||
|
||||
### Net Change pricing
|
||||
- The ability to price synthetics based off of net change
|
||||
|
||||
## 1.3.5
|
||||
|
||||
### Speed Enhancements
|
||||
- Further speed enhancements
|
||||
### Separation of implied and direct market data
|
||||
- Viewing within a market view will now show 3 columns
|
||||
- Direct
|
||||
- Implied
|
||||
- Merged
|
||||
- Ability to configure the market data size in a ladder as the following
|
||||
- Direct
|
||||
- Implied
|
||||
- Merged
|
||||
### Synthetic creation option to choose Merged or Direct
|
||||
- The ability to choose Merged or Direct per leg when creating a synthetic
|
||||
### Volume at price
|
||||
- Within a ladder, you can now see a volume at price graph
|
||||
- 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
|
||||
|
||||
|
||||
## 1.3.1 Through 1.3.4
|
||||
|
||||
### Speed Enhancements
|
||||
- Further speed enhancements
|
||||
|
||||
## 1.2
|
||||
|
||||
### Market View
|
||||
- Synthetic future leg working orders are now shown
|
||||
|
||||
### Order Book
|
||||
- Many visual facets of the Order Book grid can now be customized
|
||||
- The Order Book settings page can be accessed from the gears icon in the Order Book toolbar or from the right-click menu
|
||||
- The Expand/Collapse All button has been separated into two different buttons on the toolbar
|
||||
|
||||
### Sound Alerts
|
||||
- Sounds alerts can now be configured for various order events
|
||||
- Configure sounds under the Sound/Notification Triggers on the Order Book settings page
|
||||
|
||||
### Fills
|
||||
- Newly launched fill windows will filter out start-of-day records by default
|
||||
|
||||
### PnL
|
||||
- Prices are now formatted with penny precision
|
||||
- Day PnL has been added to the Totals footer at the bottom of the window
|
||||
|
||||
### Ladder
|
||||
- Order badges now have been improved to communicate a variety of order state
|
||||
- Background color now indicates order side
|
||||
- Child orders will display a one-character abbreviation to their parent order enclosed in a rectangle; the rectangle will have a fill color matching the parent order
|
||||
- Border color around badge will match the associated algo color
|
||||
- Exchange orders will present with a solid dot in the badge's top-right corner
|
||||
- When multiple orders reside at the same level, a solid triangle will be present in badge's bottom-right corner; all other badge decorations will not be visible in this state
|
||||
- Hedge orders will present with a vertical line inside the badge's left border
|
||||
- Trigger orders, once triggered, will no longer be shown in the ladder
|
||||
- Session High and Low visual indicators have been added to the Price column
|
||||
- Session range is now indicated in the Price column
|
||||
- A button reflecting position has been added above the quantity field
|
||||
- This button reflects the position for the currently selected account
|
||||
- Pressing the button will copy the position size to the quantity field
|
||||
- Order quantity button size values are now customizable
|
||||
- Default size preset values can now be changed, as well as added or removed
|
||||
- This can be configured per ladder instance (i.e. per security) by making use of the Custom tab
|
||||
- Synthetic ladders that were saved as part of a workspace are now restored on startup
|
||||
|
||||
### Synthetics
|
||||
- Hedge Ratio parameter has been renamed to Lean Qty Factor
|
||||
- Spreader now includes a Hidden mode parameter
|
||||
- A quoting leg can be hidden, ie an order will not be sent out until the market crosses the computed quoting price. When that happens, the quote is sent out as an IOC order.
|
||||
|
||||
### Algos
|
||||
- Trigger orders now can contain a Trigger Exit parameter based on time or price; once the exit condition has been met the Trigger order will cancel
|
||||
- IOC/FAK order type added
|
||||
- Cover order type added
|
||||
- Canceling a synthetic order no longer cancels any working hedge orders
|
||||
|
||||
### General
|
||||
- A manual page has been added to the launcher Help menu
|
||||
- Various help buttons and tooltips were added throughout the application
|
||||
- Various cash per point values updated
|
||||
- Time-based grid filters now have tick up/down buttons
|
||||
- Numerous performance improvements and stability enhancements
|
||||
- Automated position and Order Book maintenance during daily recycle
|
||||
- Individual fills will be compacted into the Start of Day position records
|
||||
- Orders from the day before the recycle will be deleted
|
||||
|
||||
|
||||
## 1.1
|
||||
|
||||
### 1.1.22
|
||||
- Order Book: Orders in Error status can now be recreated
|
||||
- Order Book: Orders containing child orders will now be collapsed by default.
|
||||
- Order Book: Expand / Collapse All button added to the right-click menu and window action pane, denoted by ▲ icon
|
||||
- Order Book: Symbol column added to allow for filtering based on group symbol
|
||||
- Time filters now have a quick Set Last Hour hyperlink which will set the Period Begin filter to (Now - 1 hour)
|
||||
|
||||
### 1.1.20
|
||||
- Update CME market data handler for price precision extension.
|
||||
https://www.cmegroup.com/confluence/display/EPICSANDBOX/Price+Precision+Extension
|
||||
|
||||
### 1.1.8
|
||||
- Enabled wild cards in risk management UI
|
||||
- Fixed spreader order updates
|
||||
|
||||
### 1.1.7
|
||||
- Fixed spread leg price fill
|
||||
- Fixed HO cash per point value
|
||||
|
||||
### 1.1.6
|
||||
- Fixed occasional crash when filtering fills
|
||||
- Fixed several cash per point multipliers
|
||||
- Colored time of sales row with aggressor side
|
||||
- Iceberg orders now cancel (rather than an error) when a child is canceled
|
||||
|
||||
### 1.1.0
|
||||
- Added per user self match protection, location and self match protection FIX tag settings
|
||||
- Implemented a new "Trigger Order" algo
|
||||
- Corrected tick formatting for a number of products
|
||||
- Added the ability to recreate canceled orders
|
||||
- Added leg fills to facilitate futures based PnL
|
||||
- PnL report now has PnL columns calculated based on your Start of Day, current day, and total position
|
||||
- Start, Day and Total PnL columns are painted with a red or green background color based on if the pnl value is negative or positive respectively
|
||||
- Corrected cash per point values, used in pnl calculations, for a number of products
|
||||
- PnL report will now remember any customizations you make to filtering and/or sorting criteria
|
||||
|
||||
+1
-1
Submodule docker/linux updated: 0afd5e92de...296f277d40.
+1
-1
Submodule externals updated: 38945c5f49...2ea5e355a6.
@@ -1,370 +1,372 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using mbts.co2;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM {
|
||||
public class Row : SecurityMDRow, IDisposable, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AnimationManager _animation = new AnimationManager();
|
||||
readonly ObservableCollection<OrderRecordImmutable> _orders = new ObservableCollection<OrderRecordImmutable>();
|
||||
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
|
||||
readonly string _security_text, _security_key_text;
|
||||
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
|
||||
readonly Property<bool> _last_price_from_spread;
|
||||
readonly CellProperty<int> _position;
|
||||
readonly CellProperty<uint> _last_size;
|
||||
readonly CellProperty<ulong> _volume;
|
||||
readonly CellProperty<bool> _inside_bids, _inside_asks;
|
||||
readonly CellProperty<int> _working_bid_volume, _working_ask_volume;
|
||||
readonly CellProperty<double> _working_best_bid, _working_best_ask;
|
||||
readonly CellProperty<string> _working_bids_text, _working_asks_text;
|
||||
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
|
||||
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
|
||||
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
|
||||
double _raw_day_position;
|
||||
string _bid_merged_size_text, _ask_merged_size_text;
|
||||
ulong _bbo_ref, _last_ref;
|
||||
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_security_text = smd.security.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
string format_price(double price) => security.ContractSpec.format(price);
|
||||
_position = new CellProperty<int>(this, nameof(position), 0);
|
||||
_bid_price = backing(nameof(bid_price), double.NaN);
|
||||
_ask_price = backing(nameof(ask_price), double.NaN);
|
||||
_last_price = backing(nameof(last_price), double.NaN);
|
||||
_last_size = new CellProperty<uint>(this, nameof(last_size), 0);
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
_session_low = backing(nameof(session_low), double.NaN);
|
||||
_session_high = backing(nameof(session_high), double.NaN);
|
||||
_last_price_from_spread = backing(nameof(last_price_from_spread), false);
|
||||
_settlement = backing(nameof(settlement), double.NaN);
|
||||
_volume = new CellProperty<ulong>(this, nameof(volume), 0);
|
||||
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
|
||||
_inside_asks = new CellProperty<bool>(this, nameof(inside_asks));
|
||||
_working_bid_volume = new CellProperty<int>(this, nameof(working_bid_volume));
|
||||
_working_ask_volume = new CellProperty<int>(this, nameof(working_ask_volume));
|
||||
//
|
||||
_bid_direct_size = backing(nameof(bid_direct_size), default(uint));
|
||||
_bid_merged_size = backing(nameof(bid_merged_size), default(uint));
|
||||
_ask_direct_size = backing(nameof(ask_direct_size), default(uint));
|
||||
_ask_merged_size = backing(nameof(ask_merged_size), default(uint));
|
||||
//
|
||||
_working_orders = new ObservableFilterCollection<OrderRecordImmutable>(_orders, o => o.is_active());
|
||||
_working_bids = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Buy);
|
||||
_working_asks = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Sell);
|
||||
_working_best_bid = new CellProperty<double>(this, nameof(working_best_bid), format_price, double.NaN);
|
||||
_working_best_ask = new CellProperty<double>(this, nameof(working_best_ask), format_price, double.NaN);
|
||||
_working_bids_text = new CellProperty<string>(this, nameof(working_bids_text), string.Empty);
|
||||
_working_asks_text = new CellProperty<string>(this, nameof(working_asks_text), string.Empty);
|
||||
//
|
||||
default_command = new DelegateCommand(_show_ladder);
|
||||
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
|
||||
bid_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidMergedSize));
|
||||
bid_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidDirectSize));
|
||||
bid_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidImpliedSize));
|
||||
working_bids_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingBids));
|
||||
ask_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Ask));
|
||||
ask_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskMergedSize));
|
||||
ask_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskDirectSize));
|
||||
ask_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskImpliedSize));
|
||||
working_asks_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingAsks));
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
|
||||
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
|
||||
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
|
||||
raise_invalidated();
|
||||
}),
|
||||
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
|
||||
);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
#region Bound Properties
|
||||
public bool inside_bids => _inside_bids.value;
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
public int position => _position.value;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
public double last_price => _last_price.Value;
|
||||
public bool last_price_from_spread => _last_price_from_spread.Value;
|
||||
public uint last_size => _last_size.value;
|
||||
public double bid_price => _bid_price.Value;
|
||||
public uint bid_direct_size => _bid_direct_size.Value;
|
||||
public uint bid_implied_size => _bid_merged_size.Value - _bid_direct_size.Value;
|
||||
public uint bid_merged_size => _bid_merged_size.Value;
|
||||
public string bid_merged_size_text => _bid_merged_size_text ?? (_bid_merged_size_text = _size_formatter(_bid_merged_size.Value, bid_implied_size));
|
||||
public double ask_price => _ask_price.Value;
|
||||
public uint ask_direct_size => _ask_direct_size.Value;
|
||||
public uint ask_implied_size => _ask_merged_size.Value - _ask_direct_size.Value;
|
||||
public uint ask_merged_size => _ask_merged_size.Value;
|
||||
public string ask_merged_size_text => _ask_merged_size_text ?? (_ask_merged_size_text = _size_formatter(_ask_merged_size.Value, ask_implied_size));
|
||||
public ulong volume => _volume.value;
|
||||
public double settlement => _settlement.Value;
|
||||
public double day_change => _day_change.Value;
|
||||
public double session_low => _session_low.Value;
|
||||
public double session_high => _session_high.Value;
|
||||
public int working_bid_volume => _working_bid_volume.value;
|
||||
public int working_ask_volume => _working_ask_volume.value;
|
||||
public string working_bids_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_bid, _order_summarizer.best_working_bid_qty, _order_summarizer.working_bid_qty, security.ContractSpec);
|
||||
public string working_asks_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_ask, _order_summarizer.best_working_ask_qty, _order_summarizer.working_ask_qty, security.ContractSpec);
|
||||
public double working_best_bid => _working_best_bid.value;
|
||||
public double working_best_ask => _working_best_ask.value;
|
||||
public IList<ui.spark.CellAnimation> animations => _animation.get_animations().ToList();
|
||||
public ICommand bid_price_click { get; }
|
||||
public ICommand bid_merged_size_click { get; }
|
||||
public ICommand bid_direct_size_click { get; }
|
||||
public ICommand bid_implied_size_click { get; }
|
||||
public ICommand working_bids_click { get; }
|
||||
public ICommand ask_price_click { get; }
|
||||
public ICommand ask_merged_size_click { get; }
|
||||
public ICommand ask_direct_size_click { get; }
|
||||
public ICommand ask_implied_size_click { get; }
|
||||
public ICommand working_asks_click { get; }
|
||||
public ICommand default_command { get; }
|
||||
public string market_description {get;}
|
||||
public string cleared_alias { get; }
|
||||
public string product_description { get; }
|
||||
public string hub_alias { get; }
|
||||
public string strip_name{ get; }
|
||||
public string mic_code { get; }
|
||||
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using mbts.co2;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM {
|
||||
public class Row : SecurityMDRow, IDisposable, IEquatable<Row>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AnimationManager _animation = new AnimationManager();
|
||||
readonly ObservableCollection<OrderRecordImmutable> _orders = new ObservableCollection<OrderRecordImmutable>();
|
||||
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
|
||||
readonly string _security_text, _security_key_text;
|
||||
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
|
||||
readonly Property<bool> _last_price_from_spread;
|
||||
readonly CellProperty<int> _position;
|
||||
readonly CellProperty<uint> _last_size;
|
||||
readonly CellProperty<ulong> _volume;
|
||||
readonly CellProperty<bool> _inside_bids, _inside_asks;
|
||||
readonly CellProperty<int> _working_bid_volume, _working_ask_volume;
|
||||
readonly CellProperty<double> _working_best_bid, _working_best_ask;
|
||||
readonly CellProperty<string> _working_bids_text, _working_asks_text;
|
||||
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
|
||||
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
|
||||
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
|
||||
double _raw_day_position;
|
||||
string _bid_merged_size_text, _ask_merged_size_text;
|
||||
ulong _bbo_ref, _last_ref;
|
||||
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_security_text = smd.security.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
string format_price(double price) => security.ContractSpec.format(price);
|
||||
_position = new CellProperty<int>(this, nameof(position), 0);
|
||||
_bid_price = backing(nameof(bid_price), double.NaN);
|
||||
_ask_price = backing(nameof(ask_price), double.NaN);
|
||||
_last_price = backing(nameof(last_price), double.NaN);
|
||||
_last_size = new CellProperty<uint>(this, nameof(last_size), 0);
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
_session_low = backing(nameof(session_low), double.NaN);
|
||||
_session_high = backing(nameof(session_high), double.NaN);
|
||||
_last_price_from_spread = backing(nameof(last_price_from_spread), false);
|
||||
_settlement = backing(nameof(settlement), double.NaN);
|
||||
_volume = new CellProperty<ulong>(this, nameof(volume), 0);
|
||||
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
|
||||
_inside_asks = new CellProperty<bool>(this, nameof(inside_asks));
|
||||
_working_bid_volume = new CellProperty<int>(this, nameof(working_bid_volume));
|
||||
_working_ask_volume = new CellProperty<int>(this, nameof(working_ask_volume));
|
||||
//
|
||||
_bid_direct_size = backing(nameof(bid_direct_size), default(uint));
|
||||
_bid_merged_size = backing(nameof(bid_merged_size), default(uint));
|
||||
_ask_direct_size = backing(nameof(ask_direct_size), default(uint));
|
||||
_ask_merged_size = backing(nameof(ask_merged_size), default(uint));
|
||||
//
|
||||
_working_orders = new ObservableFilterCollection<OrderRecordImmutable>(_orders, o => o.is_active());
|
||||
_working_bids = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Buy);
|
||||
_working_asks = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Sell);
|
||||
_working_best_bid = new CellProperty<double>(this, nameof(working_best_bid), format_price, double.NaN);
|
||||
_working_best_ask = new CellProperty<double>(this, nameof(working_best_ask), format_price, double.NaN);
|
||||
_working_bids_text = new CellProperty<string>(this, nameof(working_bids_text), string.Empty);
|
||||
_working_asks_text = new CellProperty<string>(this, nameof(working_asks_text), string.Empty);
|
||||
//
|
||||
default_command = new DelegateCommand(_show_ladder);
|
||||
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
|
||||
bid_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidMergedSize));
|
||||
bid_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidDirectSize));
|
||||
bid_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidImpliedSize));
|
||||
working_bids_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingBids));
|
||||
ask_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Ask));
|
||||
ask_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskMergedSize));
|
||||
ask_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskDirectSize));
|
||||
ask_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskImpliedSize));
|
||||
working_asks_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingAsks));
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
|
||||
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
|
||||
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
|
||||
raise_invalidated();
|
||||
}),
|
||||
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
|
||||
);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
#region Bound Properties
|
||||
public bool inside_bids => _inside_bids.value;
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
public int position => _position.value;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
public double last_price => _last_price.Value;
|
||||
public bool last_price_from_spread => _last_price_from_spread.Value;
|
||||
public uint last_size => _last_size.value;
|
||||
public double bid_price => _bid_price.Value;
|
||||
public uint bid_direct_size => _bid_direct_size.Value;
|
||||
public uint bid_implied_size => _bid_merged_size.Value - _bid_direct_size.Value;
|
||||
public uint bid_merged_size => _bid_merged_size.Value;
|
||||
public string bid_merged_size_text => _bid_merged_size_text ?? (_bid_merged_size_text = _size_formatter(_bid_merged_size.Value, bid_implied_size));
|
||||
public double ask_price => _ask_price.Value;
|
||||
public uint ask_direct_size => _ask_direct_size.Value;
|
||||
public uint ask_implied_size => _ask_merged_size.Value - _ask_direct_size.Value;
|
||||
public uint ask_merged_size => _ask_merged_size.Value;
|
||||
public string ask_merged_size_text => _ask_merged_size_text ?? (_ask_merged_size_text = _size_formatter(_ask_merged_size.Value, ask_implied_size));
|
||||
public ulong volume => _volume.value;
|
||||
public double settlement => _settlement.Value;
|
||||
public double day_change => _day_change.Value;
|
||||
public double session_low => _session_low.Value;
|
||||
public double session_high => _session_high.Value;
|
||||
public int working_bid_volume => _working_bid_volume.value;
|
||||
public int working_ask_volume => _working_ask_volume.value;
|
||||
public string working_bids_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_bid, _order_summarizer.best_working_bid_qty, _order_summarizer.working_bid_qty, security.ContractSpec);
|
||||
public string working_asks_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_ask, _order_summarizer.best_working_ask_qty, _order_summarizer.working_ask_qty, security.ContractSpec);
|
||||
public double working_best_bid => _working_best_bid.value;
|
||||
public double working_best_ask => _working_best_ask.value;
|
||||
// ice props
|
||||
public string market_description => security.market_description();
|
||||
public string cleared_alias => security.cleared_alias();
|
||||
public string product_description => security.product_name();
|
||||
public string hub_alias => security.hub_alias();
|
||||
public string strip_name => security.strip_name();
|
||||
public string mic_code => security.mic_code();
|
||||
// animations
|
||||
public IList<ui.spark.CellAnimation> animations => _animation.get_animations().ToList();
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
|
||||
#endregion
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public void refresh() {
|
||||
bool dirty = false;
|
||||
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
|
||||
_bid_price.set(b.price);
|
||||
refresh_size(ref b, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
|
||||
_ask_price.set(a.price);
|
||||
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
|
||||
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
|
||||
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
|
||||
}
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
|
||||
_last_price.set(ps.price);
|
||||
_last_price_from_spread.set(from_spread);
|
||||
_last_size.on_next_distinct(ps.size, ref dirty);
|
||||
_volume.on_next_distinct(v, ref dirty);
|
||||
}
|
||||
dirty |= _settlement.set_new(md.md.settlement_price);
|
||||
dirty |= _session_low.set_new(md.md.session_low);
|
||||
dirty |= _session_high.set_new(md.md.session_high);
|
||||
dirty |= _day_change.set_new(last_price - settlement);
|
||||
_volume.on_next_distinct(md.md.total_volume, ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
|
||||
void refresh_size(ref NativePriceSize nps, Property<uint> direct, Property<uint> merged, ref string text, string text_prop) {
|
||||
if (nps.size != merged.Value || nps.direct_size != direct.Value) {
|
||||
merged.set(nps.size);
|
||||
direct.set(nps.direct_size);
|
||||
text = null;
|
||||
dirty = true;
|
||||
RaisePropertyChanged(text_prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(Row other) =>
|
||||
other != null &&
|
||||
other.security_key == security_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as Row);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
security_key.GetHashCode();
|
||||
|
||||
#region Show Windows
|
||||
void _show_ladder() => _bus.post(SparkTags.ShowLadderTag, md);
|
||||
|
||||
void _show_order_book(MarketViewView.Columns col) {
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.WorkingBids: _show_order_book(Side.Buy); break;
|
||||
case MarketViewView.Columns.WorkingAsks: _show_order_book(Side.Sell); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _show_order_book(Side side) {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
sides = new[] { side },
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
void _show_order_ticket(MarketViewView.Columns col) {
|
||||
var order = _orders_model.factory.order(security);
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.Bid:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.BidMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Ask:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.AskMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, ask_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Last:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.LastSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, last_size));
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Callbacks
|
||||
void _on_orders(ITableUpdateTransform<OrderRecordImmutable, uint> orders) {
|
||||
foreach (var o in orders.removed)
|
||||
_order_summarizer.remove_order(o);
|
||||
foreach (var o in orders.added)
|
||||
_order_summarizer.add_order(o);
|
||||
bool dirty = false;
|
||||
_refresh_orders(ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
}
|
||||
|
||||
void _refresh_orders(ref bool dirty) {
|
||||
_position.on_next_distinct(_order_summarizer.position, ref dirty);
|
||||
_working_bid_volume.on_next_distinct(_order_summarizer.working_bid_qty, ref dirty);
|
||||
_working_ask_volume.on_next_distinct(_order_summarizer.working_ask_qty, ref dirty);
|
||||
_working_best_bid.on_next_distinct(_order_summarizer.best_working_bid, ref dirty);
|
||||
_working_best_ask.on_next_distinct(_order_summarizer.best_working_ask, ref dirty);
|
||||
_inside_bids.on_next_distinct(_order_summarizer.best_working_bid >= bid_price, ref dirty);
|
||||
_inside_asks.on_next_distinct(_order_summarizer.best_working_ask <= ask_price, ref dirty);
|
||||
|
||||
RaisePropertyChanged(
|
||||
// Implied might not be ever set, so it probably does not RaisePropertyChanged either
|
||||
// This might be part of the filter funk?
|
||||
//nameof(bid_implied_size),
|
||||
//nameof(ask_implied_size),
|
||||
nameof(working_bids_text),
|
||||
nameof(working_asks_text)
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
|
||||
class MyOrderSummarizer {
|
||||
SortedSetMap<double, OrderRecordImmutable> _bids, _asks;
|
||||
int _best_bid_working_qty, _best_ask_working_qty, _position, _working_bid_qty, _working_ask_qty;
|
||||
|
||||
public MyOrderSummarizer() {
|
||||
_bids = new SortedSetMap<double, OrderRecordImmutable>(
|
||||
OrderDataHelper.CompareUtils.bid_price_comparer,
|
||||
OrderDataHelper.CompareUtils.order_record_id_eq_comparer);
|
||||
_asks = new SortedSetMap<double, OrderRecordImmutable>(
|
||||
OrderDataHelper.CompareUtils.ask_price_comparer,
|
||||
OrderDataHelper.CompareUtils.order_record_id_eq_comparer);
|
||||
}
|
||||
|
||||
// bid
|
||||
public double best_working_bid => _bids.Count > 0 ? _bids.at_index(0).Key : double.NaN;
|
||||
public int best_working_bid_qty => _best_bid_working_qty;
|
||||
public int working_bid_qty => _working_bid_qty;
|
||||
// ask
|
||||
public double best_working_ask => _asks.Count > 0 ? _asks.at_index(0).Key : double.NaN;
|
||||
public int best_working_ask_qty => _best_ask_working_qty;
|
||||
public int working_ask_qty => _working_ask_qty;
|
||||
// pos
|
||||
public int position => _position;
|
||||
|
||||
public void add_order(OrderRecordImmutable o) {
|
||||
switch (o.side()) {
|
||||
case Side.Buy:
|
||||
_position += (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_bid_qty += o.working_quantity();
|
||||
_bids.add(o.price(), o);
|
||||
}
|
||||
break;
|
||||
case Side.Sell:
|
||||
_position -= (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_ask_qty += o.working_quantity();
|
||||
_asks.add(o.price(), o);
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_refresh();
|
||||
}
|
||||
|
||||
public void remove_order(OrderRecordImmutable o) {
|
||||
switch (o.side()) {
|
||||
case Side.Buy:
|
||||
_position -= (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_bid_qty -= o.working_quantity();
|
||||
_bids.remove(o.price(), o);
|
||||
}
|
||||
break;
|
||||
case Side.Sell:
|
||||
_position += (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_ask_qty -= o.working_quantity();
|
||||
_asks.remove(o.price(), o);
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_refresh();
|
||||
}
|
||||
|
||||
void _refresh() {
|
||||
_best_bid_working_qty = _bids.Count > 0 ? _bids.at_index(0).Sum(o => o.working_quantity()) : 0;
|
||||
_best_ask_working_qty = _asks.Count > 0 ? _asks.at_index(0).Sum(o => o.working_quantity()) : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// commands
|
||||
public ICommand bid_price_click { get; }
|
||||
public ICommand bid_merged_size_click { get; }
|
||||
public ICommand bid_direct_size_click { get; }
|
||||
public ICommand bid_implied_size_click { get; }
|
||||
public ICommand working_bids_click { get; }
|
||||
public ICommand ask_price_click { get; }
|
||||
public ICommand ask_merged_size_click { get; }
|
||||
public ICommand ask_direct_size_click { get; }
|
||||
public ICommand ask_implied_size_click { get; }
|
||||
public ICommand working_asks_click { get; }
|
||||
public ICommand default_command { get; }
|
||||
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
|
||||
#endregion
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public void refresh() {
|
||||
bool dirty = false;
|
||||
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
|
||||
_bid_price.set(b.price);
|
||||
refresh_size(ref b, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
|
||||
_ask_price.set(a.price);
|
||||
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
|
||||
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
|
||||
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
|
||||
}
|
||||
if (md.md.get_last_trade(ref _last_ref, out NativePriceSize ps, out DateTime ts, out ulong v, out bool from_spread)) {
|
||||
_last_price.set(ps.price);
|
||||
_last_price_from_spread.set(from_spread);
|
||||
_last_size.on_next_distinct(ps.size, ref dirty);
|
||||
_volume.on_next_distinct(v, ref dirty);
|
||||
}
|
||||
dirty |= _settlement.set_new(md.md.settlement_price);
|
||||
dirty |= _session_low.set_new(md.md.session_low);
|
||||
dirty |= _session_high.set_new(md.md.session_high);
|
||||
dirty |= _day_change.set_new(last_price - settlement);
|
||||
_volume.on_next_distinct(md.md.total_volume, ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
|
||||
void refresh_size(ref NativePriceSize nps, Property<uint> direct, Property<uint> merged, ref string text, string text_prop) {
|
||||
if (nps.size != merged.Value || nps.direct_size != direct.Value) {
|
||||
merged.set(nps.size);
|
||||
direct.set(nps.direct_size);
|
||||
text = null;
|
||||
dirty = true;
|
||||
RaisePropertyChanged(text_prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(Row other) =>
|
||||
other != null &&
|
||||
other.security_key == security_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as Row);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
security_key.GetHashCode();
|
||||
|
||||
#region Show Windows
|
||||
void _show_ladder() => _bus.post(SparkTags.ShowLadderTag, md);
|
||||
|
||||
void _show_order_book(MarketViewView.Columns col) {
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.WorkingBids: _show_order_book(Side.Buy); break;
|
||||
case MarketViewView.Columns.WorkingAsks: _show_order_book(Side.Sell); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _show_order_book(Side side) {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
sides = new[] { side },
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
void _show_order_ticket(MarketViewView.Columns col) {
|
||||
var order = _orders_model.factory.order(security);
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.Bid:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.BidMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.BidImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Ask:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.AskMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, ask_merged_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_direct_size));
|
||||
break;
|
||||
case MarketViewView.Columns.AskImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_implied_size));
|
||||
break;
|
||||
case MarketViewView.Columns.Last:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, 0u));
|
||||
break;
|
||||
case MarketViewView.Columns.LastSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, last_size));
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Callbacks
|
||||
void _on_orders(ITableUpdateTransform<OrderRecordImmutable, uint> orders) {
|
||||
foreach (var o in orders.removed)
|
||||
_order_summarizer.remove_order(o);
|
||||
foreach (var o in orders.added)
|
||||
_order_summarizer.add_order(o);
|
||||
bool dirty = false;
|
||||
_refresh_orders(ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
}
|
||||
|
||||
void _refresh_orders(ref bool dirty) {
|
||||
_position.on_next_distinct(_order_summarizer.position, ref dirty);
|
||||
_working_bid_volume.on_next_distinct(_order_summarizer.working_bid_qty, ref dirty);
|
||||
_working_ask_volume.on_next_distinct(_order_summarizer.working_ask_qty, ref dirty);
|
||||
_working_best_bid.on_next_distinct(_order_summarizer.best_working_bid, ref dirty);
|
||||
_working_best_ask.on_next_distinct(_order_summarizer.best_working_ask, ref dirty);
|
||||
_inside_bids.on_next_distinct(_order_summarizer.best_working_bid >= bid_price, ref dirty);
|
||||
_inside_asks.on_next_distinct(_order_summarizer.best_working_ask <= ask_price, ref dirty);
|
||||
|
||||
RaisePropertyChanged(
|
||||
// Implied might not be ever set, so it probably does not RaisePropertyChanged either
|
||||
// This might be part of the filter funk?
|
||||
//nameof(bid_implied_size),
|
||||
//nameof(ask_implied_size),
|
||||
nameof(working_bids_text),
|
||||
nameof(working_asks_text)
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
|
||||
class MyOrderSummarizer {
|
||||
SortedSetMap<double, OrderRecordImmutable> _bids, _asks;
|
||||
int _best_bid_working_qty, _best_ask_working_qty, _position, _working_bid_qty, _working_ask_qty;
|
||||
|
||||
public MyOrderSummarizer() {
|
||||
_bids = new SortedSetMap<double, OrderRecordImmutable>(
|
||||
OrderDataHelper.CompareUtils.bid_price_comparer,
|
||||
OrderDataHelper.CompareUtils.order_record_id_eq_comparer);
|
||||
_asks = new SortedSetMap<double, OrderRecordImmutable>(
|
||||
OrderDataHelper.CompareUtils.ask_price_comparer,
|
||||
OrderDataHelper.CompareUtils.order_record_id_eq_comparer);
|
||||
}
|
||||
|
||||
// bid
|
||||
public double best_working_bid => _bids.Count > 0 ? _bids.at_index(0).Key : double.NaN;
|
||||
public int best_working_bid_qty => _best_bid_working_qty;
|
||||
public int working_bid_qty => _working_bid_qty;
|
||||
// ask
|
||||
public double best_working_ask => _asks.Count > 0 ? _asks.at_index(0).Key : double.NaN;
|
||||
public int best_working_ask_qty => _best_ask_working_qty;
|
||||
public int working_ask_qty => _working_ask_qty;
|
||||
// pos
|
||||
public int position => _position;
|
||||
|
||||
public void add_order(OrderRecordImmutable o) {
|
||||
switch (o.side()) {
|
||||
case Side.Buy:
|
||||
_position += (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_bid_qty += o.working_quantity();
|
||||
_bids.add(o.price(), o);
|
||||
}
|
||||
break;
|
||||
case Side.Sell:
|
||||
_position -= (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_ask_qty += o.working_quantity();
|
||||
_asks.add(o.price(), o);
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_refresh();
|
||||
}
|
||||
|
||||
public void remove_order(OrderRecordImmutable o) {
|
||||
switch (o.side()) {
|
||||
case Side.Buy:
|
||||
_position -= (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_bid_qty -= o.working_quantity();
|
||||
_bids.remove(o.price(), o);
|
||||
}
|
||||
break;
|
||||
case Side.Sell:
|
||||
_position += (int)o.filled();
|
||||
if (o.is_active()) {
|
||||
_working_ask_qty -= o.working_quantity();
|
||||
_asks.remove(o.price(), o);
|
||||
}
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_refresh();
|
||||
}
|
||||
|
||||
void _refresh() {
|
||||
_best_bid_working_qty = _bids.Count > 0 ? _bids.at_index(0).Sum(o => o.working_quantity()) : 0;
|
||||
_best_ask_working_qty = _asks.Count > 0 ? _asks.at_index(0).Sum(o => o.working_quantity()) : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,286 +1,294 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.MarketViewWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.MarketViewView;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
|
||||
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
|
||||
|
||||
// securities_connectable should NOT be connected already.
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
_settings_manager = settings_manager;
|
||||
// Columns
|
||||
const string merged_size_col_header = "Qty", direct_size_col_header = "Direct", implied_size_col_header = "Implied", working_col_header = "Working";
|
||||
const double working_orders_col_width = 105;
|
||||
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
|
||||
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.security.cleared_alias(), nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ProductDescription, "Product Description", r => r.security.product_name(), nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.HubAlias, "Hub Alias", r => r.security.hub_alias(), nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
Width = width,
|
||||
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding,
|
||||
font_color_override = font_color,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
|
||||
|
||||
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
|
||||
|
||||
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
|
||||
|
||||
void add_hl_col(
|
||||
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
col_key, binding, new Binding(command_prop), filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
BindingBase price_binding(string prop) {
|
||||
var mb = new MultiBinding { Converter = price_converter };
|
||||
mb.Bindings.Add(new Binding(prop) { Mode = BindingMode.OneWay });
|
||||
mb.Bindings.Add(new Binding($"{nameof(Row.security)}.{nameof(SecurityImmutable.ContractSpec)}") { Mode = BindingMode.OneWay });
|
||||
return mb;
|
||||
}
|
||||
//
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
//
|
||||
_resources.AddRange(
|
||||
state.OnDispatcher().Subscribe(s => {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
if (s.grid != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
if (s.filters is FilterStates filters) {
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_merged_size_filter.load_state(filters.bid_merged_size);
|
||||
bid_direct_size_filter.load_state(filters.bid_direct_size);
|
||||
bid_implied_size_filter.load_state(filters.bid_implied_size);
|
||||
ask_price_filter.load_state(filters.ask_price);
|
||||
ask_merged_size_filter.load_state(filters.ask_merged_size);
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
position_filter.load_state(filters.position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
|
||||
}
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
|
||||
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
|
||||
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
|
||||
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
}
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public static IComparer<Row> get_column_comparer(Columns column) {
|
||||
switch (column) {
|
||||
case Columns.Security: return wrap(r => r.security);
|
||||
case Columns.SecurityKey: return wrap(r => r.security.Id);
|
||||
case Columns.Volume: return wrap(r => r.volume);
|
||||
case Columns.Last: return wrap(r => r.last_price);
|
||||
case Columns.LastSize: return wrap(r => r.last_size);
|
||||
case Columns.Position: return wrap(r => r.position);
|
||||
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
|
||||
case Columns.BidImpliedSize: return wrap(r => r.bid_implied_size);
|
||||
case Columns.BidDirectSize: return wrap(r => r.bid_direct_size);
|
||||
case Columns.BidMergedSize: return wrap(r => r.bid_merged_size);
|
||||
case Columns.Bid: return wrap(r => r.bid_price);
|
||||
case Columns.Ask: return wrap(r => r.ask_price);
|
||||
case Columns.AskMergedSize: return wrap(r => r.ask_merged_size);
|
||||
case Columns.AskDirectSize: return wrap(r => r.ask_direct_size);
|
||||
case Columns.AskImpliedSize: return wrap(r => r.ask_implied_size);
|
||||
case Columns.WorkingAsks: return wrap(r => r.working_ask_volume);
|
||||
case Columns.Settlement: return wrap(r => r.settlement);
|
||||
case Columns.DayChange: return wrap(r => r.day_change);
|
||||
case Columns.SessionLow: return wrap(r => r.session_low);
|
||||
case Columns.SessionHigh: return wrap(r => r.session_high);
|
||||
case Columns.MarketDescription: return wrap(r => r.market_description);
|
||||
case Columns.ClearedAlias: return wrap(r => r.security.cleared_alias());
|
||||
case Columns.ProductDescription: return wrap(r => r.security.product_name());
|
||||
case Columns.HubAlias: return wrap(r => r.security.hub_alias());
|
||||
case Columns.StripName: return wrap(r => r.security.strip_name());
|
||||
case Columns.MicCode: return wrap(r => r.security.mic_code());
|
||||
default: return null;
|
||||
}
|
||||
IComparer<Row> wrap<T>(Func<Row, T> selector) =>
|
||||
Comparer<T>.Default.wrap(selector);
|
||||
}
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
|
||||
|
||||
|
||||
public FilterStates get_filter_states() {
|
||||
return new FilterStates {
|
||||
security = security_filter.get_state(),
|
||||
bid_price = bid_price_filter.get_state(),
|
||||
bid_merged_size = bid_merged_size_filter.get_state(),
|
||||
bid_direct_size = bid_direct_size_filter.get_state(),
|
||||
bid_implied_size = bid_implied_size_filter.get_state(),
|
||||
ask_price = ask_price_filter.get_state(),
|
||||
ask_merged_size = ask_merged_size_filter.get_state(),
|
||||
ask_direct_size = ask_direct_size_filter.get_state(),
|
||||
ask_implied_size = ask_implied_size_filter.get_state(),
|
||||
last_price = last_price_filter.get_state(),
|
||||
last_price_from_spread = last_price_from_spread_filter.get_state(),
|
||||
last_size = last_size_filter.get_state(),
|
||||
day_change = day_change_filter.get_state(),
|
||||
settlement = settlement_filter.get_state(),
|
||||
session_low = session_low_filter.get_state(),
|
||||
session_high = session_high_filter.get_state(),
|
||||
position = position_filter.get_state(),
|
||||
raw_day_position = raw_day_position_filter.get_state(),
|
||||
working_bid_volume = working_bid_volume_filter.get_state(),
|
||||
working_ask_volume = working_ask_volume_filter.get_state(),
|
||||
volume = volume_filter.get_state(),
|
||||
security_key = security_key_filter.get_state(),
|
||||
market_description = market_description_filter.get_state(),
|
||||
cleared_alias = cleared_alias_filter.get_state(),
|
||||
product_description = product_description_filter.get_state(),
|
||||
hub_alias = hub_alias_filter.get_state(),
|
||||
strip_name = strip_name_filter.get_state(),
|
||||
mic_code = mic_code_filter.get_state(),
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.security), true); }
|
||||
|
||||
void _refresh() {
|
||||
foreach (var r in _row_vms) r.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.MarketViewWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.MarketViewView;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
|
||||
static IReadOnlyList<Func<MarketViewVM, IDataGridColumnFilter<Row>>> _filter_getters =
|
||||
ObjectSnapshot.property_delegates_of_type<MarketViewVM, IDataGridColumnFilter<Row>>();
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
|
||||
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
|
||||
|
||||
// securities_connectable should NOT be connected already.
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
_settings_manager = settings_manager;
|
||||
// Columns
|
||||
const string merged_size_col_header = "Qty", direct_size_col_header = "Direct", implied_size_col_header = "Implied", working_col_header = "Working";
|
||||
const double working_orders_col_width = 105;
|
||||
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
|
||||
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.security.cleared_alias(), nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ProductDescription, "Product Description", r => r.security.product_name(), nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.HubAlias, "Hub Alias", r => r.security.hub_alias(), nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
Width = width,
|
||||
padding = is_animatable ? AnimatableCellPadding : DefaultCellPadding,
|
||||
font_color_override = font_color,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
|
||||
|
||||
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
|
||||
|
||||
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
|
||||
|
||||
void add_hl_col(
|
||||
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
col_key, binding, new Binding(command_prop), filter, filter_template) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
sort_member_path = prop,
|
||||
font_color_override = foreground,
|
||||
background_color_override = background,
|
||||
Width = DefaultColumnWidths.Price,
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
BindingBase price_binding(string prop) {
|
||||
var mb = new MultiBinding { Converter = price_converter };
|
||||
mb.Bindings.Add(new Binding(prop) { Mode = BindingMode.OneWay });
|
||||
mb.Bindings.Add(new Binding($"{nameof(Row.security)}.{nameof(SecurityImmutable.ContractSpec)}") { Mode = BindingMode.OneWay });
|
||||
return mb;
|
||||
}
|
||||
//
|
||||
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
|
||||
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
|
||||
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
|
||||
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
|
||||
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
|
||||
};
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, col_settings_flags, row_vms);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
//
|
||||
_resources.AddRange(
|
||||
state.OnDispatcher().Subscribe(s => {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
if (s.grid != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
if (s.filters is FilterStates filters) {
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_merged_size_filter.load_state(filters.bid_merged_size);
|
||||
bid_direct_size_filter.load_state(filters.bid_direct_size);
|
||||
bid_implied_size_filter.load_state(filters.bid_implied_size);
|
||||
ask_price_filter.load_state(filters.ask_price);
|
||||
ask_merged_size_filter.load_state(filters.ask_merged_size);
|
||||
ask_direct_size_filter.load_state(filters.ask_direct_size);
|
||||
ask_implied_size_filter.load_state(filters.ask_implied_size);
|
||||
last_price_filter.load_state(filters.last_price);
|
||||
last_price_from_spread_filter.load_state(filters.last_price_from_spread);
|
||||
last_size_filter.load_state(filters.last_size);
|
||||
day_change_filter.load_state(filters.day_change);
|
||||
settlement_filter.load_state(filters.settlement);
|
||||
session_low_filter.load_state(filters.session_low);
|
||||
session_high_filter.load_state(filters.session_high);
|
||||
position_filter.load_state(filters.position);
|
||||
raw_day_position_filter.load_state(filters.raw_day_position);
|
||||
working_bid_volume_filter.load_state(filters.working_bid_volume);
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
market_description_filter.load_state(filters.market_description);
|
||||
cleared_alias_filter.load_state(filters.cleared_alias);
|
||||
product_description_filter.load_state(filters.product_description);
|
||||
hub_alias_filter.load_state(filters.hub_alias);
|
||||
strip_name_filter.load_state(filters.strip_name);
|
||||
mic_code_filter.load_state(filters.mic_code);
|
||||
}
|
||||
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
|
||||
}
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, _filters),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
}
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public static IComparer<Row> get_column_comparer(Columns column) {
|
||||
switch (column) {
|
||||
case Columns.Security: return wrap(r => r.security);
|
||||
case Columns.SecurityKey: return wrap(r => r.security.Id);
|
||||
case Columns.Volume: return wrap(r => r.volume);
|
||||
case Columns.Last: return wrap(r => r.last_price);
|
||||
case Columns.LastSize: return wrap(r => r.last_size);
|
||||
case Columns.Position: return wrap(r => r.position);
|
||||
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
|
||||
case Columns.BidImpliedSize: return wrap(r => r.bid_implied_size);
|
||||
case Columns.BidDirectSize: return wrap(r => r.bid_direct_size);
|
||||
case Columns.BidMergedSize: return wrap(r => r.bid_merged_size);
|
||||
case Columns.Bid: return wrap(r => r.bid_price);
|
||||
case Columns.Ask: return wrap(r => r.ask_price);
|
||||
case Columns.AskMergedSize: return wrap(r => r.ask_merged_size);
|
||||
case Columns.AskDirectSize: return wrap(r => r.ask_direct_size);
|
||||
case Columns.AskImpliedSize: return wrap(r => r.ask_implied_size);
|
||||
case Columns.WorkingAsks: return wrap(r => r.working_ask_volume);
|
||||
case Columns.Settlement: return wrap(r => r.settlement);
|
||||
case Columns.DayChange: return wrap(r => r.day_change);
|
||||
case Columns.SessionLow: return wrap(r => r.session_low);
|
||||
case Columns.SessionHigh: return wrap(r => r.session_high);
|
||||
case Columns.MarketDescription: return wrap(r => r.market_description);
|
||||
case Columns.ClearedAlias: return wrap(r => r.security.cleared_alias());
|
||||
case Columns.ProductDescription: return wrap(r => r.security.product_name());
|
||||
case Columns.HubAlias: return wrap(r => r.security.hub_alias());
|
||||
case Columns.StripName: return wrap(r => r.security.strip_name());
|
||||
case Columns.MicCode: return wrap(r => r.security.mic_code());
|
||||
default: return null;
|
||||
}
|
||||
IComparer<Row> wrap<T>(Func<Row, T> selector) =>
|
||||
Comparer<T>.Default.wrap(selector);
|
||||
}
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_merged_size, nameof(Row.bid_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_direct_size, nameof(Row.bid_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> bid_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.bid_implied_size, nameof(Row.bid_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> ask_price_filter { get; } = new DoubleMinMax<Row>(r => r.ask_price, nameof(Row.ask_price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_merged_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_merged_size, nameof(Row.ask_merged_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_direct_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_direct_size, nameof(Row.ask_direct_size));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> ask_implied_size_filter { get; } = new UIntMinMax<Row>(r => r.ask_implied_size, nameof(Row.ask_implied_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> last_price_filter { get; } = new DoubleMinMax<Row>(r => r.last_price, nameof(Row.last_price));
|
||||
public IDataGridColumnPersistentFilter<Row, string> last_price_from_spread_filter { get; } = new RegexFilterVM<Row>(r => r.last_price_from_spread.ToString(), nameof(Row.last_price_from_spread));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> last_size_filter { get; } = new UIntMinMax<Row>(r => r.last_size, nameof(Row.last_size));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> day_change_filter { get; } = new DoubleMinMax<Row>(r => r.day_change, nameof(Row.day_change));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> settlement_filter { get; } = new DoubleMinMax<Row>(r => r.settlement, nameof(Row.settlement));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.market_description, nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.cleared_alias, nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.product_description, nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.hub_alias, nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.strip_name, nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.mic_code, nameof(Row.mic_code));
|
||||
|
||||
IEnumerable<IDataGridColumnFilter<Row>> _filters =>
|
||||
_filter_getters.Select(fn => fn(this));
|
||||
|
||||
public FilterStates get_filter_states() {
|
||||
return new FilterStates {
|
||||
security = security_filter.get_state(),
|
||||
bid_price = bid_price_filter.get_state(),
|
||||
bid_merged_size = bid_merged_size_filter.get_state(),
|
||||
bid_direct_size = bid_direct_size_filter.get_state(),
|
||||
bid_implied_size = bid_implied_size_filter.get_state(),
|
||||
ask_price = ask_price_filter.get_state(),
|
||||
ask_merged_size = ask_merged_size_filter.get_state(),
|
||||
ask_direct_size = ask_direct_size_filter.get_state(),
|
||||
ask_implied_size = ask_implied_size_filter.get_state(),
|
||||
last_price = last_price_filter.get_state(),
|
||||
last_price_from_spread = last_price_from_spread_filter.get_state(),
|
||||
last_size = last_size_filter.get_state(),
|
||||
day_change = day_change_filter.get_state(),
|
||||
settlement = settlement_filter.get_state(),
|
||||
session_low = session_low_filter.get_state(),
|
||||
session_high = session_high_filter.get_state(),
|
||||
position = position_filter.get_state(),
|
||||
raw_day_position = raw_day_position_filter.get_state(),
|
||||
working_bid_volume = working_bid_volume_filter.get_state(),
|
||||
working_ask_volume = working_ask_volume_filter.get_state(),
|
||||
volume = volume_filter.get_state(),
|
||||
security_key = security_key_filter.get_state(),
|
||||
market_description = market_description_filter.get_state(),
|
||||
cleared_alias = cleared_alias_filter.get_state(),
|
||||
product_description = product_description_filter.get_state(),
|
||||
hub_alias = hub_alias_filter.get_state(),
|
||||
strip_name = strip_name_filter.get_state(),
|
||||
mic_code = mic_code_filter.get_state(),
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.security), true); }
|
||||
|
||||
void _refresh() {
|
||||
foreach (var r in _row_vms) r.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ namespace bts.shell.pnl {
|
||||
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
|
||||
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
|
||||
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.security?.Symbol ?? string.Empty);
|
||||
security_cell = _cell_map.add(ColumnKeys.Security, d => d.security, null, EqualityComparer<SecurityImmutable?>.Default, Comparer<SecurityImmutable>.Default.to_nullable());
|
||||
security_cell = _cell_map.add(ColumnKeys.Security, d => (d.security), null, EqualityComparer<SecurityImmutable?>.Default, Comparer<SecurityImmutable>.Default.to_nullable());
|
||||
security_key = _cell_map.add(ColumnKeys.SecurityKey, d => d.key.security_id);
|
||||
cpp = _cell_map.add(ColumnKeys.CPP, d => d.cpp);
|
||||
// sod
|
||||
|
||||
@@ -1,39 +1,43 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace mbts.spark.filters {
|
||||
static class RegexFilterUtils {
|
||||
public static Regex build_regex(string str) {
|
||||
if (str == null)
|
||||
return null;
|
||||
str = str.Trim();
|
||||
if (str == string.Empty)
|
||||
return null;
|
||||
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
}
|
||||
}
|
||||
class RegexFilterVM<TRow> : FilterVMBase<TRow, string, string> {
|
||||
string _text = string.Empty;
|
||||
Regex _regex;
|
||||
|
||||
public RegexFilterVM(Func<TRow, string> get_row_val, string prop_name = null) : base(get_row_val, prop_name) { }
|
||||
|
||||
public string text {
|
||||
get => _text;
|
||||
set => _regex = RegexFilterUtils.build_regex(_text = (value ?? string.Empty));
|
||||
}
|
||||
|
||||
public override string get_state() => _text ?? string.Empty;
|
||||
|
||||
protected override bool _get_active() => _regex != null;
|
||||
protected override bool _filter(string value) =>
|
||||
_regex != null && (
|
||||
_regex.IsMatch(value) ||
|
||||
(!string.IsNullOrEmpty(value) && _regex.IsMatch(value.Replace(" ", ""))));
|
||||
|
||||
namespace mbts.spark.filters {
|
||||
static class RegexFilterUtils {
|
||||
public static Regex build_regex(string str) {
|
||||
if (str == null)
|
||||
return null;
|
||||
str = str.Trim();
|
||||
if (str == string.Empty)
|
||||
return null;
|
||||
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
}
|
||||
}
|
||||
class RegexFilterVM<TRow> : FilterVMBase<TRow, string, string> {
|
||||
string _text = string.Empty;
|
||||
Regex _regex;
|
||||
|
||||
public RegexFilterVM(Func<TRow, string> get_row_val, string prop_name = null) : base(get_row_val, prop_name) { }
|
||||
|
||||
public string text {
|
||||
get => _text;
|
||||
set => _regex = RegexFilterUtils.build_regex(_text = (value ?? string.Empty));
|
||||
}
|
||||
|
||||
public override string get_state() => _text ?? string.Empty;
|
||||
|
||||
protected override bool _get_active() => _regex != null;
|
||||
protected override bool _filter(string value) => _regex != null && _regex.IsMatch(value);
|
||||
protected override void _load_state_internal(string state) => text = state;
|
||||
protected override void _clear() {
|
||||
_close();
|
||||
_text = string.Empty;
|
||||
_regex = null;
|
||||
RaisePropertyChanged(nameof(text));
|
||||
_raise_filter_changed();
|
||||
}
|
||||
}
|
||||
protected override void _load_state_internal(string state) => text = state;
|
||||
protected override void _clear() {
|
||||
_close();
|
||||
_text = string.Empty;
|
||||
_regex = null;
|
||||
RaisePropertyChanged(nameof(text));
|
||||
_raise_filter_changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,13 +39,18 @@ namespace bts.api {
|
||||
|
||||
static string _format(SecurityImmutable? security) {
|
||||
switch (security?.ContractCase) {
|
||||
case ContractOneofCase.Future: return format_future(security?.Symbol, security?.security_desc(), security.Value.Future);
|
||||
case ContractOneofCase.Future:
|
||||
if(security?.HasIce == true)
|
||||
return _format_ICE(security);
|
||||
return format_future(security?.Symbol, security?.security_desc(), security.Value.Future);
|
||||
case ContractOneofCase.Spread:
|
||||
if (security?.HasGlbx == true) {
|
||||
// In case we ever bring it back, pretty printed spread names can be found here:
|
||||
// GlbxSecurityFormatter.FormatSpread(security, out string result)
|
||||
return security?.Glbx.SecurityDesc;
|
||||
}
|
||||
else if(security?.HasIce == true)
|
||||
return _format_ICE(security);
|
||||
return string.Join("", _format_future_legs(SecurityHelper.get_flat_future_legs(security?.Spread.Legs)));
|
||||
case ContractOneofCase.Synthetic: return security?.Synthetic.Name;
|
||||
default: return "Unknown-security";
|
||||
@@ -79,5 +84,26 @@ namespace bts.api {
|
||||
default: return $"{fl.quantity:+0;-0}{sym_exp_name}";
|
||||
}
|
||||
}
|
||||
|
||||
static string _format_ICE (SecurityImmutable? security){
|
||||
string symbol = security?.Symbol,
|
||||
strip = security?.Ice.StripName,
|
||||
md_desc = security?.Ice.MarketDescription,
|
||||
type_append = "";
|
||||
|
||||
if(md_desc.Contains("25k ICE Lots")){
|
||||
if(md_desc.Contains("TAS"))
|
||||
type_append = "TAS";
|
||||
}
|
||||
else if(md_desc.Contains("ICE Lots")){
|
||||
if(md_desc.Contains("TAS"))
|
||||
type_append = "ICE Lots TAS";
|
||||
else
|
||||
type_append = "ICE Lots";
|
||||
}
|
||||
type_append = string.IsNullOrEmpty(type_append) ?
|
||||
" " : $" {type_append} ";
|
||||
return $"{symbol}{type_append}{strip}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<server name="default">
|
||||
<ip>63.247.113.163</ip>
|
||||
<port>3000</port>
|
||||
<userName>bluetrd-ps</userName>
|
||||
<userName>katas2f-ps</userName>
|
||||
<password>Starts123</password>
|
||||
<getStripInfoMessages>N</getStripInfoMessages>
|
||||
<strategyPreference>New</strategyPreference>
|
||||
@@ -24,8 +24,8 @@
|
||||
<server name="endex">
|
||||
<ip>63.247.113.77</ip>
|
||||
<port>3000</port>
|
||||
<userName>Value from ICE Support</userName>
|
||||
<password>Value from ICE Support</password>
|
||||
<userName>katas2f-ps</userName>
|
||||
<password>Starts123</password>
|
||||
<getStripInfoMessages>N</getStripInfoMessages>
|
||||
<strategyPreference>New</strategyPreference>
|
||||
<localNetworkInterface></localNetworkInterface>
|
||||
|
||||
@@ -105,14 +105,35 @@ message BasePositionBatch {
|
||||
google.protobuf.Timestamp current_as_of = 10; // UTC
|
||||
}
|
||||
|
||||
// security overrides /////////////////////////////////////
|
||||
message CppOverride{
|
||||
double value = 1;
|
||||
}
|
||||
|
||||
message SecurityParameters{
|
||||
uint32 security_key = 1;
|
||||
CppOverride cpp_override = 2;
|
||||
// other overrides
|
||||
}
|
||||
|
||||
message SecurityParametersBatchOp{
|
||||
repeated SecurityParameters op_params = 1;
|
||||
}
|
||||
|
||||
message SecurityParametersBatchOpResponse {
|
||||
bool ok = 1;
|
||||
string msg = 2;
|
||||
}
|
||||
|
||||
service PositionService {
|
||||
option (bts.api.service_description) = "Provides Position and PNL, per security, per account";
|
||||
|
||||
rpc create_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
|
||||
rpc update_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
|
||||
rpc remove_manual_fill (ManualFillBatch) returns (PositionServiceResponse);
|
||||
rpc set_base_position (BasePositionBatch) returns (SetBasePositionResponse);
|
||||
|
||||
rpc set_base_position (BasePositionBatch) returns (SetBasePositionResponse);
|
||||
rpc update_security_parameters (SecurityParametersBatchOp) returns (SecurityParametersBatchOpResponse);
|
||||
|
||||
// subscription
|
||||
rpc manual_fills(bts.api.VoidMessage) returns (stream ManualFill);
|
||||
rpc position_data(bts.api.VoidMessage) returns (stream PositionUnit);
|
||||
|
||||
@@ -1,136 +1,130 @@
|
||||
#
|
||||
# Common Market data options
|
||||
#
|
||||
|
||||
MarketDataConfig {
|
||||
rule: "DeploymentType.type == 'PROD'"
|
||||
value {
|
||||
use_sim: false
|
||||
}
|
||||
}
|
||||
|
||||
PublisherOptions {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
disable_heartbeat: true
|
||||
}
|
||||
}
|
||||
|
||||
PlatformMap {
|
||||
value {
|
||||
glbx_platform: "PLATFORM_MDP3ONIXS"
|
||||
ice_platform: "PLATFORM_ICEONIXS"
|
||||
}
|
||||
}
|
||||
|
||||
PublisherNamespace {
|
||||
value {
|
||||
shared_memory_namespace: @System.os_user
|
||||
}
|
||||
}
|
||||
|
||||
CMEOptions {
|
||||
value {
|
||||
feed_engine {
|
||||
name: "mdp3feed"
|
||||
# udp_socket_buffer_size: 425984
|
||||
# channels_per_thread: 1
|
||||
spin_time: 0
|
||||
}
|
||||
channel {
|
||||
id: "*"
|
||||
feed_engine: "mdp3feed"
|
||||
use_tcp_replay: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ICEPublisherOptions {
|
||||
value {
|
||||
network_interface: "eth1"
|
||||
}
|
||||
}
|
||||
|
||||
#to make this work in dev clone the operations repo
|
||||
#go here: c:\\code\\bts\\operations\\ice\\ICE_iMpactMulticastFeed_GettingStarted-1_1_36\\bin\\easyTunnel>
|
||||
#modify the easyTunnelProxy.properties file to replace
|
||||
#tunnel.multicast.interface=10.96.18.37 with your local ip
|
||||
#run easyTunnelProxy.bat BRENT-FI for Brent Full implied
|
||||
#you should see ice data
|
||||
|
||||
ICEOptions {
|
||||
value {
|
||||
channel {
|
||||
name: "oil" #these names are arbitrary and are for debugging
|
||||
market_type: 3
|
||||
uds_market_type: 3
|
||||
market_type: 4 #ICE IPE Gas Oil Futures
|
||||
uds_market_type: 4
|
||||
market_type: 5 #ICE Futures Brent, IPE Brent Futures
|
||||
uds_market_type: 5
|
||||
market_type: 9 #ICE Futures WTI, ICE WTI Crude Futures
|
||||
uds_market_type: 9
|
||||
market_type: 13 #ICE Heating Oil Futures
|
||||
uds_market_type: 13
|
||||
market_type: 15 #ICE NYH (RBOB) Gasoline Futures
|
||||
uds_market_type: 15
|
||||
##market_type: 21 #ICE Futures US Agricultures, Coffee C
|
||||
##uds_market_type: 21
|
||||
##market_type: 23 #ICE Futures US Agricultures, FCOJ A
|
||||
##uds_market_type: 23
|
||||
|
||||
##market_type: 26 #ICE Futures US Financials DX
|
||||
##uds_market_type: 26
|
||||
|
||||
uds_market_type: 11 #ICE WTI-Brent spreads
|
||||
uds_market_type: 14 #ICE Heating Oil/WTI Futures Crack
|
||||
uds_market_type: 16 #ICE NYH (RBOB) Gasoline/WTI Futures Crack
|
||||
uds_market_type: 58 #ICE Futures UI Heating Oil / Brent
|
||||
uds_market_type: 59 #ICE NYH (RBOB) Gasoline/Brent Futures Crack
|
||||
uds_market_type: 66 #ICE NYH (RBOB) Gasoline/Heating Oil Spread
|
||||
uds_market_type: 70 #ICE Heating Oil/LS Gasoil Futures Spread
|
||||
uds_market_type: 71 #ICE LS Gasoil/Brent Futures Crack
|
||||
uds_market_type: 72 #ICE NYH (RBOB) Gasoline/LS Gasoil Futures Spread
|
||||
|
||||
#onixs_log_mode: Debug
|
||||
}
|
||||
# channel {
|
||||
# name: "juice"
|
||||
# market_type: 26 #ICE Futures US Agricultures, FCOJ A
|
||||
# }
|
||||
ice_config_file: "ConnectivityConfiguration.APITest.xml"
|
||||
#ice_config_file: "ConnectivityConfiguration.PerfTest.xml"
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
delay_seconds: 0
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
value {
|
||||
delay_seconds: 120
|
||||
standard {
|
||||
start: "16:30"
|
||||
end: "16:40"
|
||||
}
|
||||
sunday {
|
||||
start: "14:00"
|
||||
end: "14:10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisplayFactorOverride {
|
||||
value {
|
||||
override {
|
||||
symbol: "SOM"
|
||||
exchange: "GLBX"
|
||||
type: "SPREAD"
|
||||
price_multiplier: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
#
|
||||
# Common Market data options
|
||||
#
|
||||
|
||||
MarketDataConfig {
|
||||
rule: "DeploymentType.type == 'PROD'"
|
||||
value {
|
||||
use_sim: false
|
||||
}
|
||||
}
|
||||
|
||||
PublisherOptions {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
disable_heartbeat: true
|
||||
}
|
||||
}
|
||||
|
||||
PlatformMap {
|
||||
value {
|
||||
glbx_platform: "PLATFORM_MDP3ONIXS"
|
||||
ice_platform: "PLATFORM_ICEONIXS"
|
||||
}
|
||||
}
|
||||
|
||||
PublisherNamespace {
|
||||
value {
|
||||
shared_memory_namespace: @System.os_user
|
||||
}
|
||||
}
|
||||
|
||||
CMEOptions {
|
||||
value {
|
||||
feed_engine {
|
||||
name: "mdp3feed"
|
||||
# udp_socket_buffer_size: 425984
|
||||
# channels_per_thread: 1
|
||||
spin_time: 0
|
||||
}
|
||||
channel {
|
||||
id: "*"
|
||||
feed_engine: "mdp3feed"
|
||||
use_tcp_replay: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#to make this work in dev clone the operations repo
|
||||
#go here: c:\\code\\bts\\operations\\ice\\ICE_iMpactMulticastFeed_GettingStarted-1_1_36\\bin\\easyTunnel>
|
||||
#modify the easyTunnelProxy.properties file to replace
|
||||
#tunnel.multicast.interface=10.96.18.37 with your local ip
|
||||
#run easyTunnelProxy.bat BRENT-FI for Brent Full implied
|
||||
#you should see ice data
|
||||
|
||||
ICEOptions {
|
||||
value {
|
||||
channel {
|
||||
name: "oil" #these names are arbitrary and are for debugging
|
||||
market_type: 3
|
||||
uds_market_type: 3
|
||||
market_type: 4 #ICE IPE Gas Oil Futures
|
||||
uds_market_type: 4
|
||||
market_type: 5 #ICE Futures Brent, IPE Brent Futures
|
||||
uds_market_type: 5
|
||||
market_type: 9 #ICE Futures WTI, ICE WTI Crude Futures
|
||||
uds_market_type: 9
|
||||
market_type: 13 #ICE Heating Oil Futures
|
||||
uds_market_type: 13
|
||||
market_type: 15 #ICE NYH (RBOB) Gasoline Futures
|
||||
uds_market_type: 15
|
||||
##market_type: 21 #ICE Futures US Agricultures, Coffee C
|
||||
##uds_market_type: 21
|
||||
##market_type: 23 #ICE Futures US Agricultures, FCOJ A
|
||||
##uds_market_type: 23
|
||||
|
||||
##market_type: 26 #ICE Futures US Financials DX
|
||||
##uds_market_type: 26
|
||||
|
||||
uds_market_type: 11 #ICE WTI-Brent spreads
|
||||
uds_market_type: 14 #ICE Heating Oil/WTI Futures Crack
|
||||
uds_market_type: 16 #ICE NYH (RBOB) Gasoline/WTI Futures Crack
|
||||
uds_market_type: 58 #ICE Futures UI Heating Oil / Brent
|
||||
uds_market_type: 59 #ICE NYH (RBOB) Gasoline/Brent Futures Crack
|
||||
uds_market_type: 66 #ICE NYH (RBOB) Gasoline/Heating Oil Spread
|
||||
uds_market_type: 70 #ICE Heating Oil/LS Gasoil Futures Spread
|
||||
uds_market_type: 71 #ICE LS Gasoil/Brent Futures Crack
|
||||
uds_market_type: 72 #ICE NYH (RBOB) Gasoline/LS Gasoil Futures Spread
|
||||
|
||||
#onixs_log_mode: Debug
|
||||
}
|
||||
# channel {
|
||||
# name: "juice"
|
||||
# market_type: 26 #ICE Futures US Agricultures, FCOJ A
|
||||
# }
|
||||
ice_config_file: "ConnectivityConfiguration.APITest.xml"
|
||||
#ice_config_file: "ConnectivityConfiguration.PerfTest.xml"
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
rule: "DeploymentType.type == 'dev'"
|
||||
value {
|
||||
delay_seconds: 0
|
||||
}
|
||||
}
|
||||
|
||||
MDBootDelayWindow {
|
||||
value {
|
||||
delay_seconds: 120
|
||||
standard {
|
||||
start: "16:30"
|
||||
end: "16:40"
|
||||
}
|
||||
sunday {
|
||||
start: "14:00"
|
||||
end: "14:10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisplayFactorOverride {
|
||||
value {
|
||||
override {
|
||||
symbol: "SOM"
|
||||
exchange: "GLBX"
|
||||
type: "SPREAD"
|
||||
price_multiplier: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,10 @@ message EnsureOnScreenWithWorkAreaRect {
|
||||
optional bool enabled = 1 [default = false];
|
||||
}
|
||||
|
||||
message CustomFormatIceSecurities{
|
||||
optional bool enabled = 1 [default = true];
|
||||
}
|
||||
|
||||
message IgnoreNansInDayPnL {
|
||||
optional bool enabled = 1 [default = true];
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
|
||||
me->sec = serv.sec_db.resolve(SecurityKey(def.security_key()));
|
||||
me->previous_working_position = Quantity(0);
|
||||
|
||||
auto const &native_state = get_native_state(me->state);
|
||||
auto &native_state = get_native_state(me->state);
|
||||
switch (me->state.status()) {
|
||||
|
||||
case ORDER_STATUS_WORKING:
|
||||
@@ -202,6 +202,8 @@ void NativeOrder::on_init_recovered(OrderRecord const &rec,
|
||||
}
|
||||
// if we made it this far we are good.
|
||||
me->routing_record = rq.routing_record;
|
||||
*native_state.mutable_routing_record() = rq.routing_record;
|
||||
|
||||
if (me->state.status() != ORDER_STATUS_PENDING_WORKING)
|
||||
me->exchange_order_id = native_state.exchange_order_id();
|
||||
init_fix();
|
||||
|
||||
@@ -215,13 +215,6 @@ api::orderman::OrderDefinition
|
||||
ICENativeOrder::on_init_from_exe_report(const ExecutionReport& er, const PreActivationOrderServices& os)
|
||||
{
|
||||
auto k = er.order_key;
|
||||
auto nextid = me->services->gen_id();
|
||||
if(k.value() < nextid)
|
||||
{
|
||||
k = OrderKey(nextid);
|
||||
LOG(OrderMan, Error, "Updating recovered order id with newly generated id {}", k.value());
|
||||
}
|
||||
|
||||
OrderRecord orecord;
|
||||
orecord.set_key(k.value());
|
||||
orecord.set_parent_key(null_order_key().value());
|
||||
|
||||
@@ -1 +1 @@
|
||||
set(sources algo/AlgoDefinitionServiceServer.cpp;cmds/AdminReport.cpp;cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/FIXSim.cpp;cmds/Help.cpp;cmds/KeyCode.cpp;cmds/Md.cpp;cmds/ParseHistoricalFIX.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/MultipartFormDecoder.cpp;http/WebServer.cpp;http/tests/MultiPartFormTests.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/PositionMap.cpp;position/PositionServicePNLHelper.cpp;position/PositionServiceServer.cpp;position/import/BasePositionServiceServer.cpp;position/import/DownloadCommonStateMachine.cpp;position/import/DownloadWindow.cpp;position/import/MoneyFileServiceServer.cpp;position/import/parsing/gmi_parser.cpp;position/import/parsing/money_parser.cpp;position/import/parsing/parser_common.cpp;position/tests/PositionTests.cpp;position/tests/position_tests_internal.cpp;subprocess/SubProcessRunner.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/FIXSim.cpp;cmds/Help.cpp;cmds/KeyCode.cpp;cmds/Md.cpp;cmds/ParseHistoricalFIX.cpp;cmds/PrintHedgeOrders.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.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/MultipartFormDecoder.cpp;http/WebServer.cpp;http/tests/MultiPartFormTests.cpp;main.cpp;md/ChannelVolume.cpp;md/MarketDataAuxiliaryServiceServer.cpp;md/VolumeAtPriceState.cpp;md/platforms/IMDPlatform.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/PositionMap.cpp;position/PositionServicePNLHelper.cpp;position/PositionServiceServer.cpp;position/import/BasePositionServiceServer.cpp;position/import/DownloadCommonStateMachine.cpp;position/import/DownloadWindow.cpp;position/import/MoneyFileServiceServer.cpp;position/import/parsing/gmi_parser.cpp;position/import/parsing/money_parser.cpp;position/import/parsing/parser_common.cpp;position/tests/PositionTests.cpp;position/tests/position_tests_internal.cpp;subprocess/SubProcessRunner.cpp;support/ErrorReportService.cpp;support/ShellLatencyServer.cpp;user/AdminServiceServer.cpp;user/AuthServiceServer.cpp;user/tests/admin_service_tests.cpp)
|
||||
@@ -1,26 +0,0 @@
|
||||
#include "TestCeloxica.h"
|
||||
|
||||
#include "server/sparkd/md/platforms/celoxica/CXMain.h"
|
||||
#include "server/sparkd/md/platforms/celoxica/CXListener.h"
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace cmd
|
||||
{
|
||||
|
||||
int TestCeloxica::execute(const std::vector<std::string>&)
|
||||
{
|
||||
bts::md::CXMain main;
|
||||
main.initialize();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string TestCeloxica::help()
|
||||
{
|
||||
return "\t test BTS <> Celoxica integration \n";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ICmd.h"
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace cmd
|
||||
{
|
||||
|
||||
class TestCeloxica : public ICmd
|
||||
{
|
||||
public:
|
||||
int execute(std::vector<std::string> const &args);
|
||||
std::string help();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "server/sparkd/cmds/RunSim.h"
|
||||
#include "server/sparkd/cmds/Span.h"
|
||||
#include "server/sparkd/cmds/Test.h"
|
||||
#include "server/sparkd/cmds/TestCeloxica.h"
|
||||
#include "server/sparkd/cmds/Web.h"
|
||||
|
||||
using namespace bts;
|
||||
@@ -32,7 +31,6 @@ int main(int argc, char **argv) {
|
||||
REGISTER(Help);
|
||||
REGISTER(Run);
|
||||
REGISTER(Md);
|
||||
REGISTER(TestCeloxica);
|
||||
REGISTER(ConfigCheck);
|
||||
REGISTER(KeyCode);
|
||||
REGISTER(RunSim);
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
#include "CXListener.h"
|
||||
|
||||
#include "lib/utils/defer.h"
|
||||
#include "lib/utils/CallTimer.h"
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include <tuple>
|
||||
|
||||
namespace celoxica { namespace gmac {} }
|
||||
#include "celoxica.h"
|
||||
using namespace celoxica::gmac;
|
||||
using namespace std;
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace md
|
||||
{
|
||||
|
||||
CXListener::CXListener(CXSecurityIndex& index, MDProducer& producer, SecurityIDRecorder& recorder,
|
||||
co2::SettlementRecorder& settles, std::vector<MDProducerChannel> channels,
|
||||
utils::Dispatcher& disp) :
|
||||
_index(index),
|
||||
_producer(producer),
|
||||
_packet_arrived(),
|
||||
_latest_sequence(0),
|
||||
_security_recorder(recorder),
|
||||
_settlement_recorder(settles),
|
||||
_symbol_filter_storage(),
|
||||
_channels(channels),
|
||||
_background(disp),
|
||||
_security_recorder_input_timer(disp, 900, [&]() { dequeue_securities(); }),
|
||||
_security_recorder_input(),
|
||||
_concurrency_monitor(0),
|
||||
_book_updates()
|
||||
{
|
||||
_book_updates.reserve(32);
|
||||
_security_recorder_input_timer.start();
|
||||
}
|
||||
|
||||
CXListener::~CXListener()
|
||||
{
|
||||
_security_recorder_input_timer.stop();
|
||||
}
|
||||
|
||||
void CXListener::add_software_filter(const std::string& symbol)
|
||||
{
|
||||
_symbol_filter_storage.push_back(symbol);
|
||||
//_symbol_filter.clear();
|
||||
//for (const auto& str : _symbol_filter_storage) {
|
||||
// _symbol_filter.insert(onixs::StringRef(str));
|
||||
//}
|
||||
}
|
||||
|
||||
void CXListener::on_GMACMessageOrderDepthV3(const GMACMessageOrderDepthV3*)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void CXListener::dequeue_securities()
|
||||
{
|
||||
}
|
||||
|
||||
void CXListener::flush_books()
|
||||
{
|
||||
// This is called at the end of the packet
|
||||
// Write all book updates to shared memory and reset for the next packet
|
||||
//for (auto& entry : _book_updates) {
|
||||
// // bbo first
|
||||
// uint16_t sequence = (_latest_sequence & 0xFFFF);
|
||||
// entry.handle.update_bbo(_packet_arrived, entry.bids[0], entry.asks[0], sequence);
|
||||
|
||||
// // Perf Note: the remainder of the book update could be done (carefully) with a memcpy
|
||||
// for (size_t x = 1; x < COCONUT_MAX_DEPTH; ++x) {
|
||||
// entry.handle.update_bid(_packet_arrived, entry.bids[x], x);
|
||||
// entry.handle.update_ask(_packet_arrived, entry.asks[x], x);
|
||||
// }
|
||||
//}
|
||||
//// Reset pending updates
|
||||
//_book_updates.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
md::CXListener::BookData::BookData(MDProducerHandle & handle) : handle(handle), bids(), asks()
|
||||
{
|
||||
}
|
||||
|
||||
}//namespace
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/utils/bts_no_copy.h"
|
||||
#include "lib/md/coconut2.h"
|
||||
#include "lib/utils/Dispatcher.h"
|
||||
#include "server/sparkd/md/platforms/celoxica/CXSecurityIndex.h"
|
||||
|
||||
#include <tbb/concurrent_queue.h>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace md
|
||||
{
|
||||
struct GMACMessageOrderDepthV3;
|
||||
|
||||
class CXListener
|
||||
{
|
||||
public:
|
||||
CXListener(CXSecurityIndex&, MDProducer&, SecurityIDRecorder&,
|
||||
co2::SettlementRecorder&, std::vector<MDProducerChannel>, utils::Dispatcher&);
|
||||
~CXListener();
|
||||
|
||||
void add_software_filter(const std::string& symbol);
|
||||
|
||||
void on_GMACMessageOrderDepthV3(const GMACMessageOrderDepthV3*);
|
||||
|
||||
private:
|
||||
void flush_books();
|
||||
void dequeue_securities();
|
||||
|
||||
private:
|
||||
BTS_NO_COPY(CXListener);
|
||||
|
||||
CXSecurityIndex& _index;
|
||||
MDProducer& _producer;
|
||||
utils::TimeStamp _packet_arrived;
|
||||
uint64_t _latest_sequence;
|
||||
SecurityIDRecorder& _security_recorder;
|
||||
co2::SettlementRecorder& _settlement_recorder;
|
||||
std::vector<std::string> _symbol_filter_storage;
|
||||
std::vector<MDProducerChannel> _channels;
|
||||
utils::Dispatcher& _background;
|
||||
utils::DispatcherTimer _security_recorder_input_timer;
|
||||
tbb::concurrent_queue<CXSecurityData*> _security_recorder_input;
|
||||
std::atomic<int> _concurrency_monitor;
|
||||
|
||||
struct BookData {
|
||||
BookData(MDProducerHandle& handle);
|
||||
MDProducerHandle handle;
|
||||
PriceSize bids[10];
|
||||
PriceSize asks[10];
|
||||
};
|
||||
|
||||
std::vector<BookData> _book_updates;
|
||||
};
|
||||
|
||||
|
||||
}}//namespace
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
#include "CXMain.h"
|
||||
|
||||
#include "lib/md/cme/MDP3Utils.h"
|
||||
#include "lib/config/config.h"
|
||||
#include "lib/md/coconut2.h"
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/utils/Dispatcher.h"
|
||||
#include "lib/utils/proto_helper.h"
|
||||
#include "lib/utils/helper_macros.h"
|
||||
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/config/protos/system.pb.h"
|
||||
|
||||
namespace celoxica { namespace gmac {} }
|
||||
#include "celoxica.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#define __retcode_start__
|
||||
#define __retcode_end__
|
||||
}
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
using namespace std;
|
||||
using namespace celoxica::gmac;
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace md
|
||||
{
|
||||
|
||||
void run_cx_server(UserSymbol* psyms, size_t symcount)
|
||||
{
|
||||
CXMain main;
|
||||
vector<UserSymbol> symbols;
|
||||
for(size_t x = 0; x < symcount; ++x) {
|
||||
symbols.push_back(psyms[x]);
|
||||
}
|
||||
main.run(symbols);
|
||||
}
|
||||
|
||||
CXMain::CXMain() :
|
||||
_producer(co2::PLATFORM_CELOXICA),
|
||||
_security_recorder({}),
|
||||
_settlement_recorder(co2::PLATFORM_CELOXICA),
|
||||
_listeners()
|
||||
{
|
||||
}
|
||||
|
||||
CXMain::~CXMain() {}
|
||||
|
||||
void CXMain::initialize()
|
||||
{
|
||||
//celoxica::gmac::GMACIdentify(stderr);
|
||||
//const auto& options = config::get<CMEOptions>();
|
||||
|
||||
}
|
||||
|
||||
|
||||
void CXMain::run(std::vector<UserSymbol>& symbols)
|
||||
{
|
||||
bts::md::await_cme_config();
|
||||
initialize();
|
||||
|
||||
parse_channels(symbols);
|
||||
// const auto& channels = parse_channels(symbols);
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::unique_ptr<CXListener> CXMain::prepare_listener(const std::string& channel, const CMEChannel&)
|
||||
{
|
||||
UNUSED(channel);
|
||||
return std::unique_ptr<CXListener>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/utils/bts_no_copy.h"
|
||||
#include "lib/md/coconut2.h"
|
||||
#include "server/sparkd/md/platforms/celoxica/CXListener.h"
|
||||
#include "server/sparkd/md/platforms/celoxica/CXSecurityIndex.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace md
|
||||
{
|
||||
void run_cx_server(UserSymbol* sym, size_t symcount);
|
||||
|
||||
struct CMEChannel;
|
||||
class CXListener;
|
||||
|
||||
/*
|
||||
* The CX provider is very simple. 1 CXMain maps to 1 Celoxica Queue, which maps to 1 thread and one bound CPU.
|
||||
* Control starts in CXMain which starts the thread, binds to a cpu, provides a (currently unused) list of all the symbols we need
|
||||
* prepare_handler configures an exchange channel to be owned by this queue, and prepare_listener sets up thing that sees
|
||||
* all callbacks which maps the exchange id to the MDProducerHandle and writes to shared mem
|
||||
*/
|
||||
class CXMain
|
||||
{
|
||||
public:
|
||||
CXMain();
|
||||
~CXMain();
|
||||
|
||||
void run(std::vector<UserSymbol>&); //call initialize, start the pump
|
||||
void initialize(); //set up channels, etc
|
||||
|
||||
std::unique_ptr<CXListener> prepare_listener(const std::string& channel, const CMEChannel&);
|
||||
|
||||
private:
|
||||
BTS_NO_COPY(CXMain);
|
||||
|
||||
CXSecurityIndex _index;
|
||||
MDProducer _producer;
|
||||
SecurityIDRecorder _security_recorder;
|
||||
co2::SettlementRecorder _settlement_recorder;
|
||||
std::vector<std::unique_ptr<CXListener>> _listeners;
|
||||
};
|
||||
|
||||
}}//namespace
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/md/SecurityIndex.h"
|
||||
#include <string>
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace md
|
||||
{
|
||||
|
||||
struct CXSecurityData
|
||||
{
|
||||
CXSecurityData(uint32_t id, MDProducerHandle& h) : id(id), handle(h) {}
|
||||
uint32_t id; // exchange id
|
||||
MDProducerHandle handle;
|
||||
};
|
||||
|
||||
using CXSecurityIndex = ConcurrentSecurityIndex<uint32_t, CXSecurityData>;
|
||||
|
||||
}}//namespace
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace celoxica
|
||||
{
|
||||
namespace gmac
|
||||
{
|
||||
|
||||
#include "celoxica/gmac.h"
|
||||
#include "celoxica/gmac_template_v3.h"
|
||||
#include "celoxica/gmac_info.h"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
// #include "catch/catch.hpp"
|
||||
|
||||
// #include "lib/config/config.h"
|
||||
// #include "lib/config/protos/MarketData.pb.h"
|
||||
|
||||
// #include <vector>
|
||||
// #include <string>
|
||||
// #include <functional>
|
||||
// #include <stdarg.h>
|
||||
|
||||
// #include "../MDP3Utils.h"
|
||||
// #include "../onixs.h"
|
||||
|
||||
// using namespace bts::md;
|
||||
// using namespace std;
|
||||
|
||||
|
||||
// namespace bts {
|
||||
// namespace md {
|
||||
|
||||
// map<string, onixs::FeedEngineSettings> create_feed_engine_settings(const std::vector<CMEChannel>& channels);
|
||||
// const CMEOptions::CMEChannel channel_settings_for(const string& id);
|
||||
|
||||
// namespace test {
|
||||
|
||||
// vector<CMEChannel> test_channels(int count, ...) {
|
||||
// // Doing this the C way because I still haven't updated VS
|
||||
// vector<CMEChannel> result;
|
||||
// va_list ap;
|
||||
// va_start(ap, count);
|
||||
// for (int x = 0; x < count; ++x) {
|
||||
// const char* val = va_arg(ap, char*);
|
||||
// CMEChannel temp;
|
||||
// temp.id = val;
|
||||
// result.push_back(temp);
|
||||
// }
|
||||
// va_end(ap);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
|
||||
// TEST_CASE("mdp3_tests","[md]") {
|
||||
// SECTION("test_defaults") {
|
||||
// config::internal::initialize_for_test("CMEOptions {}");
|
||||
|
||||
// vector<CMEChannel> channels = test_channels(1, "310");
|
||||
// auto result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 0);
|
||||
|
||||
// channels = test_channels(2, "310", "311");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 0);
|
||||
|
||||
// channels = test_channels(4, "310", "311", "400", "412");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 0);
|
||||
|
||||
// REQUIRE(!channel_settings_for("310").has_feed_engine());
|
||||
// REQUIRE(!channel_settings_for("311").has_feed_engine());
|
||||
// REQUIRE(!channel_settings_for("315").has_feed_engine());
|
||||
// }
|
||||
|
||||
// SECTION("test_segregate_futures") {
|
||||
// config::internal::initialize_for_test(
|
||||
// "CMEOptions {\n"
|
||||
// " value {\n"
|
||||
// " feed_engine {\n"
|
||||
// " name: 'default'\n"
|
||||
// " }\n"
|
||||
// " feed_engine {\n"
|
||||
// " name: 'futures'\n"
|
||||
// " num_threads: 4\n"
|
||||
// " }\n"
|
||||
// " # Futures\n"
|
||||
// " channel {\n"
|
||||
// " id: '310'\n"
|
||||
// " id: '312'\n"
|
||||
// " feed_engine: 'futures'\n"
|
||||
// " }\n"
|
||||
// " # Options\n"
|
||||
// " channel {\n"
|
||||
// " id: '311'\n"
|
||||
// " id: '313'\n"
|
||||
// " id: '315'\n"
|
||||
// " feed_engine: 'default'\n"
|
||||
// " }\n"
|
||||
// " }\n"
|
||||
// "}");
|
||||
|
||||
// vector<CMEChannel> channels = test_channels(1, "310");
|
||||
// auto result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 1);
|
||||
// REQUIRE(result.begin()->first == "futures");
|
||||
// REQUIRE(result.begin()->second.threadsCount() == 4);
|
||||
|
||||
// channels = test_channels(1, "311");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 1);
|
||||
// REQUIRE(result.begin()->first == "default");
|
||||
// REQUIRE(result.begin()->second.threadsCount() == 1);
|
||||
|
||||
// channels = test_channels(2, "311", "310");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 2);
|
||||
// REQUIRE(result["default"].threadsCount() == 1);
|
||||
// REQUIRE(result["futures"].threadsCount() == 4);
|
||||
|
||||
// // Test channels not listed in config
|
||||
// channels = test_channels(2, "317", "318");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 0);
|
||||
// }
|
||||
|
||||
// SECTION("test_no_default") {
|
||||
// config::internal::initialize_for_test(
|
||||
// "CMEOptions {\n"
|
||||
// " value {\n"
|
||||
// " feed_engine {\n"
|
||||
// " name: 'futures'\n"
|
||||
// " num_threads: 4\n"
|
||||
// " }\n"
|
||||
// " # any channel not listed will use the default feed engine\n"
|
||||
// " # Futures\n"
|
||||
// " channel {\n"
|
||||
// " id: '310'\n"
|
||||
// " id: '312'\n"
|
||||
// " feed_engine: 'futures'\n"
|
||||
// " }\n"
|
||||
// " # Options\n"
|
||||
// " channel {\n"
|
||||
// " id: '311'\n"
|
||||
// " id: '313'\n"
|
||||
// " id: '315'\n"
|
||||
// " use_tcp_replay: false\n"
|
||||
// " }\n"
|
||||
// " }\n"
|
||||
// "}");
|
||||
|
||||
// vector<CMEChannel> channels = test_channels(1, "310");
|
||||
// auto result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 1);
|
||||
// REQUIRE(result.begin()->first == "futures");
|
||||
// REQUIRE(result.begin()->second.threadsCount() == 4);
|
||||
|
||||
// channels = test_channels(1, "311");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 0);
|
||||
|
||||
// channels = test_channels(2, "311", "310");
|
||||
// result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 1);
|
||||
// REQUIRE(result["futures"].threadsCount() == 4);
|
||||
|
||||
|
||||
// REQUIRE(channel_settings_for("310").use_tcp_replay() == true);
|
||||
// REQUIRE(channel_settings_for("311").use_tcp_replay() == false);
|
||||
// REQUIRE(channel_settings_for("315").use_tcp_replay() == false);
|
||||
// }
|
||||
|
||||
// SECTION("test_one_thread_each") {
|
||||
// config::internal::initialize_for_test(
|
||||
// "CMEOptions {\n"
|
||||
// " value {\n"
|
||||
// " channel {\n"
|
||||
// " id: '*'\n"
|
||||
// " }\n"
|
||||
// " }\n"
|
||||
// "}\n");
|
||||
|
||||
// vector<CMEChannel> channels = test_channels(1, "310", "311");
|
||||
// auto result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 0);
|
||||
|
||||
// const auto& settings = channel_settings_for("311");
|
||||
// REQUIRE(!settings.has_feed_engine());
|
||||
// }
|
||||
|
||||
// SECTION("test_common_futures_thread") {
|
||||
// config::internal::initialize_for_test(
|
||||
// "CMEOptions {\n"
|
||||
// " value {\n"
|
||||
// " feed_engine {\n"
|
||||
// " name: 'futures'\n"
|
||||
// " num_threads: 4\n"
|
||||
// " }\n"
|
||||
// " channel {\n"
|
||||
// " id: '310'\n"
|
||||
// " feed_engine: 'futures'\n"
|
||||
// " udp_socket_buffer_size: 1234\n"
|
||||
// " }\n"
|
||||
// " channel {\n"
|
||||
// " id: '*'\n"
|
||||
// " }\n"
|
||||
// " }\n"
|
||||
// "}\n");
|
||||
|
||||
// vector<CMEChannel> channels = test_channels(6, "310", "311", "315", "100", "10", "42");
|
||||
// auto result = create_feed_engine_settings(channels);
|
||||
// REQUIRE(result.size() == 1);
|
||||
// result = create_feed_engine_settings(test_channels(1, "315"));
|
||||
// REQUIRE(result.size() == 0);
|
||||
|
||||
// REQUIRE(!channel_settings_for("311").has_feed_engine());
|
||||
// REQUIRE(channel_settings_for("310").has_feed_engine());
|
||||
// REQUIRE(channel_settings_for("310").udp_socket_buffer_size() == 1234);
|
||||
// REQUIRE(channel_settings_for("311").has_udp_socket_buffer_size() == false);
|
||||
// }
|
||||
// }
|
||||
|
||||
// }}}//namespace
|
||||
@@ -1,421 +1,451 @@
|
||||
#include "ICEListener.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <vector>
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace bts::co2;
|
||||
using namespace bts::md::ice;
|
||||
|
||||
namespace bts {
|
||||
namespace md {
|
||||
namespace ice {
|
||||
|
||||
template <>
|
||||
double double_of_price<onixs_ice::Rational>(const onixs_ice::Rational& price, int8_t denominator)
|
||||
{
|
||||
UNUSED(denominator);
|
||||
double dbl = price.convertTo<double>();
|
||||
return dbl;
|
||||
}
|
||||
|
||||
template<>
|
||||
void ICEListener::set_security_specific<onixs_ice::FuturesProductDefinition>(const onixs_ice::FuturesProductDefinition& def,
|
||||
FixedSecurity& fsec)
|
||||
{
|
||||
//fsec.cash_per_point = double_of_price(def.tickValue, fsec.ice_price_denominator);
|
||||
//fsec.cash_per_point = def.tickValue;
|
||||
fsec.cash_per_point = def.unitQuantity;
|
||||
}
|
||||
|
||||
ICEListener::ICEListener(ICESecurityIndex& index, MDProducer& producer, const std::vector<MDProducerChannel> channels) :
|
||||
_index(index),
|
||||
_producer(producer),
|
||||
_symbol_filter(),
|
||||
_channels(channels),
|
||||
_concurrency_monitor(0)
|
||||
{
|
||||
}
|
||||
|
||||
ICEListener::~ICEListener() {}
|
||||
|
||||
std::string ICEListener::parse_contract_symbol(const std::string& sym)
|
||||
{
|
||||
auto end = sym.find(' ');
|
||||
if(end == std::string::npos)
|
||||
return sym;
|
||||
else
|
||||
return sym.substr(0, end);
|
||||
}
|
||||
|
||||
//
|
||||
//State change
|
||||
void ICEListener::onHandlerStateChange(const onixs_ice::HandlerStateChange&)
|
||||
{
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStateChange(const onixs_ice::MarketStateChange& , const onixs_ice::MessageInfo&)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Warnings/Errors
|
||||
void ICEListener::onError(const onixs_ice::Error& error)
|
||||
{
|
||||
std::string error_desc(error.description());
|
||||
LOG(md,Warn, "OnixS ICE Error: {} - {}", error.toString(), error_desc );
|
||||
}
|
||||
void ICEListener::onFeedError(onixs_ice::FeedId feedid, onixs_ice::ErrorCode code, const std::string& desc)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE FeedError: {} - {}", feedid, desc, code);
|
||||
}
|
||||
void ICEListener::onWarning(const onixs_ice::Warning& warning)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE Warn: {} - {}", warning.toString(), std::string(warning.description()) );
|
||||
}
|
||||
|
||||
//
|
||||
// Security discovery
|
||||
void ICEListener::onFuturesProductDefinition(const onixs_ice::FuturesProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
if(msg.isSpread)
|
||||
on_future_strip(msg, is_interested);
|
||||
else
|
||||
on_future(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onNewExpiry(const onixs_ice::NewExpiry& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_future(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onFuturesStrategyDefinition(const onixs_ice::FuturesStrategyDefinition& def, bool& is_interested)
|
||||
{
|
||||
on_uds_future_spread(def, is_interested);
|
||||
}
|
||||
//This is only for UDS
|
||||
void ICEListener::onNewFuturesStrategyDefinition(const onixs_ice::NewFuturesStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_uds_future_spread(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onOptionsProductDefinition(const onixs_ice::OptionsProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsMarketDefinition(const onixs_ice::NewOptionsMarketDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
void ICEListener::onOptionsStrategyDefinition(const onixs_ice::OptionsStrategyDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsStrategyDefinition(const onixs_ice::NewOptionsStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void ICEListener::onMarketSnapshot(const onixs_ice::MarketSnapshot& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
if (msg.settlePriceDateTime > 0) {
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(msg.settlePriceDateTime) );
|
||||
}
|
||||
|
||||
LOG(md,Diag,"OnixSICE Got Market Snap : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), datetime::today() );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
|
||||
data->handle.set_last_trade( ts, pricesize_of_price_size(msg.lastTradePrice, deal_denominator, msg.lastTradeQuantity),
|
||||
ll_of_timestamp(msg.lastTradeDateTime), msg.volume );
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStatistics(const onixs_ice::MarketStatistics& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
data->handle.set_total_volume(msg.volume);
|
||||
}
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotOrder(const onixs_ice::MarketSnapshotOrder& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotPriceLevel(const onixs_ice::MarketSnapshotPriceLevel& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
// Book
|
||||
void ICEListener::onOrderBookUpdated(const onixs_ice::OrderBook& onixs_book, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
//tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
|
||||
|
||||
auto data = _index.get_security(onixs_book.marketId());
|
||||
if (data == nullptr)
|
||||
return;
|
||||
bts_assert(data->id == onixs_book.marketId());
|
||||
auto denominator = data->handle.security().ice_price_denominator;
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = onixs_book.bids().size();
|
||||
size_t asks_size = onixs_book.offers().size();
|
||||
if (!data->handle.has_depth())
|
||||
{
|
||||
bids_size = std::min(bids_size, size_t { 1 });
|
||||
asks_size = std::min(asks_size, size_t { 1 });
|
||||
}
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dbids; dbids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> ibids; ibids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dasks; dasks.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> iasks; iasks.fill(NO_MARKET);
|
||||
for (size_t x = 0; x < bids_size; ++x)
|
||||
{
|
||||
auto& bid = onixs_book.bids()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dbids[x] = pricesize_of_price_size(bid.price, denominator, bid.quantity - bid.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
ibids[x] = pricesize_of_price_size(bid.price, denominator, bid.impliedQuantity);
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x)
|
||||
{
|
||||
auto& ask = onixs_book.offers()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dasks[x] = pricesize_of_price_size(ask.price, denominator, ask.quantity - ask.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
iasks[x] = pricesize_of_price_size(ask.price, denominator, ask.impliedQuantity);
|
||||
}
|
||||
data->handle.update_books(TimeStamp::now(), dbids, dasks, ibids, iasks, (uint32_t) msg_info.sequenceNumber);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Trades
|
||||
AggressorSide map_aggressor(onixs_ice::AggressorSide::Enum agside)
|
||||
{
|
||||
switch(agside) {
|
||||
default:
|
||||
case onixs_ice::AggressorSide::NoAggressor:
|
||||
return AGGRESSOR_SIDE_NONE;
|
||||
case onixs_ice::AggressorSide::Buy:
|
||||
return AGGRESSOR_SIDE_BUY;
|
||||
case onixs_ice::AggressorSide::Sell:
|
||||
return AGGRESSOR_SIDE_SELL;
|
||||
}
|
||||
}
|
||||
|
||||
void on_trade(ICESecurityData* data, const onixs_ice::Price& price, int size,
|
||||
onixs_ice::AggressorSide::Enum agside, const onixs_ice::DateTime& onixs_date, bool from_spread)
|
||||
{
|
||||
auto ts = utils::TimeStamp::now();
|
||||
PriceSize ps = pricesize_of_price_size(price, data->handle.security().ice_deal_denominator, size);
|
||||
auto date = ll_of_timestamp(onixs_date);
|
||||
data->handle.add_trade(ts, ps, date, map_aggressor(agside), 0u, from_spread);
|
||||
}
|
||||
|
||||
void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != NULL)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto size = msg.quantity;
|
||||
auto price = msg.price;
|
||||
onixs_ice::AggressorSide::Enum agside = msg.aggressorSide;
|
||||
on_trade(data, price, size, agside, msg.transactDateTime, msg.isSystemPricedLeg);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// RFQ
|
||||
void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
co2::QuoteRequestMessage message;
|
||||
message.set_key(data->handle.key());
|
||||
|
||||
auto time = ll_of_timestamp(msg.messageTimestamp);
|
||||
message.set_time(time);
|
||||
|
||||
long long rfqid = msg.rfqSystemId;
|
||||
message.set_request_id(std::to_string(rfqid));
|
||||
|
||||
//https://ref.onixs.biz/cpp-ice-impact-handler-guide/struct_onix_s_1_1_i_c_e_1_1i_mpact_1_1_market_data_1_1_side.html#a8150b7776c2a1749101acf22e868d091
|
||||
switch(msg.side)
|
||||
{
|
||||
case ' ':
|
||||
message.set_side(co2::QuoteRequestMessage::UNDEFINED);
|
||||
break;
|
||||
case '1':
|
||||
message.set_side(co2::QuoteRequestMessage::BUY);
|
||||
break;
|
||||
case '2':
|
||||
message.set_side(co2::QuoteRequestMessage::SELL);
|
||||
break;
|
||||
}
|
||||
message.set_size(msg.quantity);
|
||||
data->handle.add_rfq(ts, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
void ICEListener::onSettlementPrice(const onixs_ice::SettlementPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto price = msg.settlementPrice;
|
||||
auto time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_settle_denominator;
|
||||
LOG(md,Diag,"OnixSICE Got Settlement: {} - {}", std::to_string(data->id), std::to_string(price));
|
||||
data->handle.update_settlement(ts, convert_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onOpenInterest(const onixs_ice::OpenInterest &msg, const onixs_ice::MessageInfo &msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.openPrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.closePrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_closing( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(time) );
|
||||
|
||||
LOG(md,Warn,"OnixSICE Got EOD Market : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), date_of_timestamp(time) );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Feed
|
||||
void ICEListener::onMulticastMessageBlockBegin(onixs_ice::FeedId feedId, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
UNUSED(feedId);
|
||||
}
|
||||
|
||||
void ICEListener::onMulticastMessageBlockEnd(onixs_ice::FeedId feedId, const onixs_ice::MarketIds& marketIds)
|
||||
{
|
||||
UNUSED(feedId);
|
||||
UNUSED(marketIds);
|
||||
}
|
||||
|
||||
void ICEListener::onFeedStarted(onixs_ice::FeedId, const onixs_ice::MarketIds&)
|
||||
{}
|
||||
|
||||
void ICEListener::onFeedStopped(onixs_ice::FeedId)
|
||||
{}
|
||||
|
||||
void ICEListener::onSessionNumberChange(onixs_ice::FeedId , short , short )
|
||||
{}
|
||||
|
||||
|
||||
//
|
||||
// Non-onixs calls
|
||||
void ICEListener::add_filter_symbol(const std::string& symbol)
|
||||
{
|
||||
_symbol_filter.insert(symbol);
|
||||
}
|
||||
|
||||
void ICEListener::heartbeat()
|
||||
{
|
||||
for (auto& c : _channels) { c.heartbeat(); }
|
||||
}
|
||||
|
||||
//
|
||||
// Private functions
|
||||
void ICEListener::add_security(const FixedSecurity& security)
|
||||
{
|
||||
// Set up the shared memory for this security
|
||||
auto handle = _producer.add_security(security);
|
||||
bts_assert(security.numeric_id != 0);
|
||||
// Add it to the index so it can receive messages
|
||||
_index.add_security(security.numeric_id, handle);
|
||||
}
|
||||
|
||||
void ICEListener::update_all_status(CoconutSecurityStatus status)
|
||||
{
|
||||
_index.walk_security_list([=](ICESecurityData& data)
|
||||
{
|
||||
data.handle.set_status(status);
|
||||
});
|
||||
}
|
||||
|
||||
}}} //namespace
|
||||
|
||||
|
||||
#include "ICEListener.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include <vector>
|
||||
#include "lib/utils/helper_macros.h"
|
||||
#include "lib/config/protos/MarketData.pb.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace bts::co2;
|
||||
using namespace bts::md::ice;
|
||||
|
||||
namespace bts {
|
||||
namespace md {
|
||||
namespace ice {
|
||||
|
||||
template <>
|
||||
double double_of_price<onixs_ice::Rational>(const onixs_ice::Rational& price, int8_t denominator)
|
||||
{
|
||||
UNUSED(denominator);
|
||||
double dbl = price.convertTo<double>();
|
||||
return dbl;
|
||||
}
|
||||
|
||||
template<>
|
||||
void ICEListener::set_security_specific<onixs_ice::FuturesProductDefinition>(const onixs_ice::FuturesProductDefinition& def,
|
||||
FixedSecurity& fsec)
|
||||
{
|
||||
//fsec.cash_per_point = double_of_price(def.tickValue, fsec.ice_price_denominator);
|
||||
//fsec.cash_per_point = def.tickValue;
|
||||
fsec.cash_per_point = def.unitQuantity;
|
||||
}
|
||||
|
||||
ICEListener::ICEListener(ICESecurityIndex& index, MDProducer& producer, const std::vector<MDProducerChannel> channels) :
|
||||
_index(index),
|
||||
_producer(producer),
|
||||
_symbol_filter(),
|
||||
_channels(channels),
|
||||
_concurrency_monitor(0)
|
||||
{
|
||||
}
|
||||
|
||||
ICEListener::~ICEListener() {}
|
||||
|
||||
std::string ICEListener::parse_contract_symbol(const std::string& sym)
|
||||
{
|
||||
auto end = sym.find(' ');
|
||||
if(end == std::string::npos)
|
||||
return sym;
|
||||
else
|
||||
return sym.substr(0, end);
|
||||
}
|
||||
|
||||
//
|
||||
//State change
|
||||
void ICEListener::onHandlerStateChange(const onixs_ice::HandlerStateChange&)
|
||||
{
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStateChange(const onixs_ice::MarketStateChange& , const onixs_ice::MessageInfo&)
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Warnings/Errors
|
||||
void ICEListener::onError(const onixs_ice::Error& error)
|
||||
{
|
||||
std::string error_desc(error.description());
|
||||
LOG(md,Warn, "OnixS ICE Error: {} - {}", error.toString(), error_desc );
|
||||
}
|
||||
void ICEListener::onFeedError(onixs_ice::FeedId feedid, onixs_ice::ErrorCode code, const std::string& desc)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE FeedError: {} - {}", feedid, desc, code);
|
||||
}
|
||||
void ICEListener::onWarning(const onixs_ice::Warning& warning)
|
||||
{
|
||||
LOG(md,Warn, "OnixS ICE Warn: {} - {}", warning.toString(), std::string(warning.description()) );
|
||||
}
|
||||
|
||||
//
|
||||
// Security discovery
|
||||
void ICEListener::onFuturesProductDefinition(const onixs_ice::FuturesProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
if(msg.isSpread)
|
||||
on_future_strip(msg, is_interested);
|
||||
else
|
||||
on_future(msg, is_interested);
|
||||
LOG(md,Info,"ICE_Future,{},{},{},{},{},{},{},{},{},{},{},{},{}",
|
||||
is_interested,
|
||||
msg.productName, // NG Swing GDD Futures,
|
||||
msg.hubAlias, // Henry
|
||||
msg.marketDesc, // NG Swing GDD Futures - Henry - 10 Jul 20,
|
||||
msg.stripName, // 10 Jul 20
|
||||
msg.contractSymbol, // CGS SDN1020.N1020,
|
||||
msg.productId,
|
||||
msg.marketId,
|
||||
msg.requestMarketType,
|
||||
msg.securitySubType,
|
||||
msg.contractSymbolExtra,
|
||||
msg.clearedAlias,
|
||||
msg.isSpread);
|
||||
}
|
||||
|
||||
void ICEListener::onNewExpiry(const onixs_ice::NewExpiry& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_future(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onFuturesStrategyDefinition(const onixs_ice::FuturesStrategyDefinition& msg, bool& is_interested)
|
||||
{
|
||||
on_uds_future_spread(msg, is_interested);
|
||||
LOG(md,Info,"ICE_Strategy,{},{},{},{},{},{},{},{},{},{},{},{},{},{}",
|
||||
is_interested,
|
||||
msg.productName,
|
||||
msg.hubAlias,
|
||||
msg.marketDesc,
|
||||
msg.stripName,
|
||||
msg.contractSymbol,
|
||||
msg.productId,
|
||||
msg.marketId,
|
||||
msg.requestMarketType,
|
||||
msg.securitySubType,
|
||||
log::CharPtrWrapper {""},
|
||||
msg.clearedAlias,
|
||||
msg.productName,
|
||||
0);
|
||||
}
|
||||
|
||||
void ICEListener::onNewFuturesStrategyDefinition(const onixs_ice::NewFuturesStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
on_uds_future_spread(msg, is_interested);
|
||||
}
|
||||
|
||||
void ICEListener::onOptionsProductDefinition(const onixs_ice::OptionsProductDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsMarketDefinition(const onixs_ice::NewOptionsMarketDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
void ICEListener::onOptionsStrategyDefinition(const onixs_ice::OptionsStrategyDefinition& msg, bool& is_interested)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
}
|
||||
void ICEListener::onNewOptionsStrategyDefinition(const onixs_ice::NewOptionsStrategyDefinition& msg, bool& is_interested, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(is_interested);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void ICEListener::onMarketSnapshot(const onixs_ice::MarketSnapshot& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
if (msg.settlePriceDateTime > 0) {
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(msg.settlePriceDateTime) );
|
||||
}
|
||||
|
||||
LOG(md,Diag,"OnixSICE Got Market Snap : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), datetime::today() );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
|
||||
data->handle.set_last_trade( ts, pricesize_of_price_size(msg.lastTradePrice, deal_denominator, msg.lastTradeQuantity),
|
||||
ll_of_timestamp(msg.lastTradeDateTime), msg.volume );
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onMarketStatistics(const onixs_ice::MarketStatistics& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg_info);
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
data->handle.set_total_volume(msg.volume);
|
||||
}
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotOrder(const onixs_ice::MarketSnapshotOrder& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
// Gap Recovery -- This is not necessary if we subscribe to the onOrderBookUpdated callback, they handle the breaks transparently
|
||||
void ICEListener::onMarketSnapshotPriceLevel(const onixs_ice::MarketSnapshotPriceLevel& msg, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
//
|
||||
// Book
|
||||
void ICEListener::onOrderBookUpdated(const onixs_ice::OrderBook& onixs_book, const onixs_ice::MessageInfo& msg_info)
|
||||
{
|
||||
//tracepoint (bts_trace,trace_order,(char*) "onBookUpdate", (uint32_t)msg_info.sequenceNumber, (uint16_t) msg_info.sequenceNumber, 0);
|
||||
|
||||
auto data = _index.get_security(onixs_book.marketId());
|
||||
if (data == nullptr)
|
||||
return;
|
||||
bts_assert(data->id == onixs_book.marketId());
|
||||
auto denominator = data->handle.security().ice_price_denominator;
|
||||
|
||||
// Overwrite all entries every time
|
||||
size_t bids_size = onixs_book.bids().size();
|
||||
size_t asks_size = onixs_book.offers().size();
|
||||
if (!data->handle.has_depth())
|
||||
{
|
||||
bids_size = std::min(bids_size, size_t { 1 });
|
||||
asks_size = std::min(asks_size, size_t { 1 });
|
||||
}
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dbids; dbids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> ibids; ibids.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_DEPTH> dasks; dasks.fill(NO_MARKET);
|
||||
std::array<PriceSize,COCONUT_MAX_IMPLIED_DEPTH> iasks; iasks.fill(NO_MARKET);
|
||||
for (size_t x = 0; x < bids_size; ++x)
|
||||
{
|
||||
auto& bid = onixs_book.bids()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dbids[x] = pricesize_of_price_size(bid.price, denominator, bid.quantity - bid.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
ibids[x] = pricesize_of_price_size(bid.price, denominator, bid.impliedQuantity);
|
||||
}
|
||||
for (size_t x = 0; x < asks_size; ++x)
|
||||
{
|
||||
auto& ask = onixs_book.offers()[x];
|
||||
if(x < COCONUT_MAX_DEPTH)
|
||||
dasks[x] = pricesize_of_price_size(ask.price, denominator, ask.quantity - ask.impliedQuantity);
|
||||
if(x < COCONUT_MAX_IMPLIED_DEPTH)
|
||||
iasks[x] = pricesize_of_price_size(ask.price, denominator, ask.impliedQuantity);
|
||||
}
|
||||
data->handle.update_books(TimeStamp::now(), dbids, dasks, ibids, iasks, (uint32_t) msg_info.sequenceNumber);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Trades
|
||||
AggressorSide map_aggressor(onixs_ice::AggressorSide::Enum agside)
|
||||
{
|
||||
switch(agside) {
|
||||
default:
|
||||
case onixs_ice::AggressorSide::NoAggressor:
|
||||
return AGGRESSOR_SIDE_NONE;
|
||||
case onixs_ice::AggressorSide::Buy:
|
||||
return AGGRESSOR_SIDE_BUY;
|
||||
case onixs_ice::AggressorSide::Sell:
|
||||
return AGGRESSOR_SIDE_SELL;
|
||||
}
|
||||
}
|
||||
|
||||
void on_trade(ICESecurityData* data, const onixs_ice::Price& price, int size,
|
||||
onixs_ice::AggressorSide::Enum agside, const onixs_ice::DateTime& onixs_date, bool from_spread)
|
||||
{
|
||||
auto ts = utils::TimeStamp::now();
|
||||
PriceSize ps = pricesize_of_price_size(price, data->handle.security().ice_deal_denominator, size);
|
||||
auto date = ll_of_timestamp(onixs_date);
|
||||
data->handle.add_trade(ts, ps, date, map_aggressor(agside), 0u, from_spread);
|
||||
}
|
||||
|
||||
void ICEListener::onTrade(const onixs_ice::Trade& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != NULL)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto size = msg.quantity;
|
||||
auto price = msg.price;
|
||||
onixs_ice::AggressorSide::Enum agside = msg.aggressorSide;
|
||||
on_trade(data, price, size, agside, msg.transactDateTime, msg.isSystemPricedLeg);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// RFQ
|
||||
void ICEListener::onRfq(const onixs_ice::Rfq& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
co2::QuoteRequestMessage message;
|
||||
message.set_key(data->handle.key());
|
||||
|
||||
auto time = ll_of_timestamp(msg.messageTimestamp);
|
||||
message.set_time(time);
|
||||
|
||||
long long rfqid = msg.rfqSystemId;
|
||||
message.set_request_id(std::to_string(rfqid));
|
||||
|
||||
//https://ref.onixs.biz/cpp-ice-impact-handler-guide/struct_onix_s_1_1_i_c_e_1_1i_mpact_1_1_market_data_1_1_side.html#a8150b7776c2a1749101acf22e868d091
|
||||
switch(msg.side)
|
||||
{
|
||||
case ' ':
|
||||
message.set_side(co2::QuoteRequestMessage::UNDEFINED);
|
||||
break;
|
||||
case '1':
|
||||
message.set_side(co2::QuoteRequestMessage::BUY);
|
||||
break;
|
||||
case '2':
|
||||
message.set_side(co2::QuoteRequestMessage::SELL);
|
||||
break;
|
||||
}
|
||||
message.set_size(msg.quantity);
|
||||
data->handle.add_rfq(ts, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
void ICEListener::onSettlementPrice(const onixs_ice::SettlementPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto price = msg.settlementPrice;
|
||||
auto time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_settle_denominator;
|
||||
LOG(md,Diag,"OnixSICE Got Settlement: {} - {}", std::to_string(data->id), std::to_string(price));
|
||||
data->handle.update_settlement(ts, convert_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onOpenInterest(const onixs_ice::OpenInterest &msg, const onixs_ice::MessageInfo &msg_info)
|
||||
{
|
||||
UNUSED(msg);
|
||||
UNUSED(msg_info);
|
||||
}
|
||||
|
||||
void ICEListener::onOpenPrice(const onixs_ice::OpenPrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.openPrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onClosePrice(const onixs_ice::ClosePrice& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::Price price = msg.closePrice;
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_closing( utils::TimeStamp::now(), double_of_price(price, denominator), date_of_timestamp(time));
|
||||
}
|
||||
}
|
||||
|
||||
void ICEListener::onEndOfDayMarketSummary(const onixs_ice::EndOfDayMarketSummary& msg, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
auto* data = _index.get_security(msg.marketId);
|
||||
if (data != nullptr)
|
||||
{
|
||||
bts_assert(data->id == msg.marketId);
|
||||
onixs_ice::DateTime time = msg.dateTime;
|
||||
auto ts = utils::TimeStamp::now();
|
||||
auto settle_denominator = data->handle.security().ice_settle_denominator;
|
||||
data->handle.update_settlement( ts, convert_price(msg.settlementPrice, settle_denominator), date_of_timestamp(time) );
|
||||
|
||||
LOG(md,Warn,"OnixSICE Got EOD Market : {} - {}", std::to_string(data->id), std::to_string(msg.settlementPrice));
|
||||
|
||||
auto deal_denominator = data->handle.security().ice_deal_denominator;
|
||||
data->handle.update_opening( ts, double_of_price(msg.openingPrice, deal_denominator), date_of_timestamp(time) );
|
||||
data->handle.update_session_high( ts, double_of_price(msg.high, deal_denominator) );
|
||||
data->handle.update_session_low( ts, double_of_price(msg.low, deal_denominator) );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Feed
|
||||
void ICEListener::onMulticastMessageBlockBegin(onixs_ice::FeedId feedId, const onixs_ice::MessageInfo& )
|
||||
{
|
||||
UNUSED(feedId);
|
||||
}
|
||||
|
||||
void ICEListener::onMulticastMessageBlockEnd(onixs_ice::FeedId feedId, const onixs_ice::MarketIds& marketIds)
|
||||
{
|
||||
UNUSED(feedId);
|
||||
UNUSED(marketIds);
|
||||
}
|
||||
|
||||
void ICEListener::onFeedStarted(onixs_ice::FeedId, const onixs_ice::MarketIds&)
|
||||
{}
|
||||
|
||||
void ICEListener::onFeedStopped(onixs_ice::FeedId)
|
||||
{}
|
||||
|
||||
void ICEListener::onSessionNumberChange(onixs_ice::FeedId , short , short )
|
||||
{}
|
||||
|
||||
|
||||
//
|
||||
// Non-onixs calls
|
||||
void ICEListener::add_filter_symbol(const std::string& symbol)
|
||||
{
|
||||
LOG(md, Info, "ICEListener::add_filter_symbol: adding {}", symbol);
|
||||
_symbol_filter.insert(symbol);
|
||||
}
|
||||
|
||||
void ICEListener::heartbeat()
|
||||
{
|
||||
for (auto& c : _channels) { c.heartbeat(); }
|
||||
}
|
||||
|
||||
//
|
||||
// Private functions
|
||||
void ICEListener::add_security(const FixedSecurity& security)
|
||||
{
|
||||
// Set up the shared memory for this security
|
||||
auto handle = _producer.add_security(security);
|
||||
bts_assert(security.numeric_id != 0);
|
||||
// Add it to the index so it can receive messages
|
||||
_index.add_security(security.numeric_id, handle);
|
||||
}
|
||||
|
||||
void ICEListener::update_all_status(CoconutSecurityStatus status)
|
||||
{
|
||||
_index.walk_security_list([=](ICESecurityData& data)
|
||||
{
|
||||
data.handle.set_status(status);
|
||||
});
|
||||
}
|
||||
|
||||
}}} //namespace
|
||||
|
||||
|
||||
@@ -119,7 +119,20 @@ private:
|
||||
enum SecurityFilterAction { ADD, SKIP, DELETE };
|
||||
|
||||
std::string parse_contract_symbol(const std::string& sym);
|
||||
|
||||
template<typename Definition>
|
||||
std::string get_contract_symbol(const Definition& def){
|
||||
const auto& sym = def.contractSymbol;
|
||||
// TODO : this is only exception right now but for HHD sstrips
|
||||
// def.contractSymbol is empty !
|
||||
if (sym.size() == 0 && def.marketDesc.rfind("NG Swing GDD Futures - Henry", 0) == 0)
|
||||
return "HHD";
|
||||
auto end = sym.find(' ');
|
||||
if(end == std::string::npos)
|
||||
return sym;
|
||||
else
|
||||
return sym.substr(0, end);
|
||||
}
|
||||
|
||||
template<typename Definition>
|
||||
SecurityFilterAction filter_security(int exchange_id, const Definition& def)
|
||||
{
|
||||
@@ -130,7 +143,7 @@ private:
|
||||
}
|
||||
if(_symbol_filter.count("*") > 0)
|
||||
return ADD;
|
||||
return _symbol_filter.count( parse_contract_symbol(def.contractSymbol) ) > 0 ? ADD : SKIP;
|
||||
return _symbol_filter.count(get_contract_symbol(def)) > 0 ? ADD : SKIP;
|
||||
}
|
||||
void flush_books();
|
||||
void update_all_status(co2::CoconutSecurityStatus status);
|
||||
@@ -146,7 +159,7 @@ template<typename Definition>
|
||||
void ICEListener::set_security_common(const Definition& def, FixedSecurity& fsec)
|
||||
{
|
||||
fsec.numeric_id = def.marketId;
|
||||
std::string sym = parse_contract_symbol(def.contractSymbol);
|
||||
std::string sym = get_contract_symbol(def);
|
||||
if(!sym.empty())
|
||||
{
|
||||
fsec.symbol( sym );
|
||||
@@ -165,7 +178,7 @@ void ICEListener::set_security_common(const Definition& def, FixedSecurity& fsec
|
||||
fsec.ice_settle_denominator = (int8_t)(def.settlePriceDenominator - '0');
|
||||
double min_tick = double_of_price(def.incrementPrice, fsec.ice_price_denominator);
|
||||
fsec.low_tick = fsec.high_tick = min_tick;
|
||||
fsec.price_break = 0.0;
|
||||
fsec.price_break = 0.0;
|
||||
|
||||
fsec.ice_market_description(def.marketDesc);
|
||||
fsec.ice_cleared_alias(def.clearedAlias);
|
||||
@@ -207,12 +220,12 @@ void ICEListener::on_uds_future_spread(const Definition& def, bool& is_intereste
|
||||
// auto expiration = date_of_maturity(sec);
|
||||
// if (expiration < datetime::today()) return;
|
||||
|
||||
FixedSpreadSecurityL fsec;
|
||||
FixedSpreadSecurityXL fsec;
|
||||
set_security_common(def, fsec);
|
||||
fsec.alt_symbol(def.strategySymbol);
|
||||
|
||||
const auto& legs = def.legs;
|
||||
if (legs.size() > 0) {
|
||||
const auto& legs = def.legs;
|
||||
if (legs.size() > 0 && legs.size() <= 512) {
|
||||
fsec.set_leg_count(legs.size());
|
||||
for(size_t x = 0; x < fsec.leg_count(); ++x)
|
||||
{
|
||||
@@ -238,6 +251,8 @@ void ICEListener::on_uds_future_spread(const Definition& def, bool& is_intereste
|
||||
add_security(fsec);
|
||||
is_interested = true;
|
||||
}
|
||||
else
|
||||
LOG(md, Error, "Ignoring security {} with {} legs", def.marketDesc, legs.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -248,7 +263,7 @@ void ICEListener::on_future_strip(const FutureDefinition& def, bool& is_interest
|
||||
auto exchange_id = def.marketId;
|
||||
if (filter_security(exchange_id, def) != ADD) return;
|
||||
|
||||
FixedSpreadSecurityXS fsec;
|
||||
FixedSpreadSecurityXL fsec;
|
||||
set_security_common(def, fsec);
|
||||
fsec.alt_symbol(def.stripName);
|
||||
|
||||
|
||||
@@ -73,31 +73,17 @@ onixs_ice::MarketSubscriptions market_subcription_for_channel(const ICEChannel&
|
||||
onixs_ice::MarketSubscriptions subs;
|
||||
for(const auto& x : chan.market_types)
|
||||
{
|
||||
//onixs_ice::MarketSubType::NonImplied
|
||||
if(x == 26)
|
||||
{
|
||||
onixs_ice::MarketSubscription futures(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::Futures, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(futures);
|
||||
}
|
||||
else
|
||||
{
|
||||
onixs_ice::MarketSubscription futures(x, onixs_ice::MarketSubType::FullImplied, onixs_ice::SecurityType::Futures, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(futures);
|
||||
}
|
||||
onixs_ice::MarketSubscription futures(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::Futures, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(futures);
|
||||
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
}
|
||||
for(const auto& x : chan.uds_market_types)
|
||||
{
|
||||
if(x == 26)
|
||||
{
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
}
|
||||
else
|
||||
{
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::FullImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
}
|
||||
}
|
||||
onixs_ice::MarketSubscription spreads(x, onixs_ice::MarketSubType::NonImplied, onixs_ice::SecurityType::UdsFuturesMarkets, onixs_ice::BookDepth::FOD);
|
||||
subs.push_back(spreads);
|
||||
}
|
||||
return subs;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ struct PositionServicePNLHelper::PImpl {
|
||||
void PositionServicePNLHelper::compute_pnl(
|
||||
const std::map<int, PositionUnit const*>& in_pos,
|
||||
const std::map<std::string,uint32_t>& account_map,
|
||||
const std::map<uint32_t,double>& cpp_override_map,
|
||||
std::vector<PNLUnit>& out_pnl,
|
||||
std::vector<AccountPNL>& out_account_pnl){
|
||||
out_account_pnl.clear();
|
||||
@@ -145,6 +146,8 @@ void PositionServicePNLHelper::compute_pnl(
|
||||
const PositionUnit& pu = *(p.second);
|
||||
uint32_t sec_key = pu.security_id();
|
||||
double cpp = me->handle_index.get_cpp(SecurityKey(sec_key));
|
||||
auto it = cpp_override_map.find(sec_key);
|
||||
cpp = (it != cpp_override_map.end()) ? it->second : cpp;
|
||||
double bid, ask, last, settle;
|
||||
me->handle_index.get_md(SecurityKey(sec_key),bid,ask,last,settle);
|
||||
PNLUnit pnlu;
|
||||
|
||||
@@ -17,7 +17,7 @@ private:
|
||||
std::unique_ptr<PImpl> me;
|
||||
private:
|
||||
double _compute_day_pnl (const bts::api::position::PNLUnit&);
|
||||
double _get_cpp (uint32_t);
|
||||
//double _get_cpp (uint32_t);
|
||||
// aggregates [sec, account]->pnl tp [account]->pnl
|
||||
void _aggregate_account_pnl(
|
||||
const std::vector<api::position::PNLUnit>& in_pnl,
|
||||
@@ -29,6 +29,7 @@ private:
|
||||
~PositionServicePNLHelper();
|
||||
void compute_pnl(const std::map<int, bts::api::position::PositionUnit const*>& in_pos,
|
||||
const std::map<std::string,uint32_t>& account_map,
|
||||
const std::map<uint32_t,double>& cpp_override_map,
|
||||
std::vector<bts::api::position::PNLUnit>& out_pnl,
|
||||
std::vector<bts::api::position::AccountPNL>& out_account_pnl);
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ using namespace bts::api::position;
|
||||
using namespace bts::md;
|
||||
|
||||
static auto const manual_fills_ns = "manual_fills";
|
||||
static auto const security_params_ns = "security_parameters";
|
||||
|
||||
struct PositionServiceServer::PImpl {
|
||||
bts::orderman::PositionMap state;
|
||||
@@ -37,6 +38,7 @@ struct PositionServiceServer::PImpl {
|
||||
int id;
|
||||
// manual fills management
|
||||
std::unique_ptr<persistence::IStore> manual_fills_store;
|
||||
std::unique_ptr<persistence::IStore> security_params_store;
|
||||
PositionServiceBase::Manual_fillsStatefulPublisher manual_fills_publisher;
|
||||
// pnl
|
||||
PositionServiceBase::Pnl_dataStatefulPublisher pnl_publisher;
|
||||
@@ -47,6 +49,7 @@ struct PositionServiceServer::PImpl {
|
||||
bts::api::admin::AdminService admin_service;
|
||||
api::admin::AdminData admin_data;
|
||||
std::map<std::string, uint32_t> account_map;
|
||||
std::map<uint32_t, double> cpp_override_map;
|
||||
|
||||
PImpl(
|
||||
bts::api::ServiceContext &context,
|
||||
@@ -64,6 +67,8 @@ struct PositionServiceServer::PImpl {
|
||||
// manual fills
|
||||
manual_fills_store(
|
||||
std::make_unique<persistence::SqliteStore>("manual_fills")),
|
||||
security_params_store(
|
||||
std::make_unique<persistence::SqliteStore>("security_parameters")),
|
||||
manual_fills_publisher(manual_fills_publisher),
|
||||
// pnl
|
||||
pnl_publisher(pnl_publisher),
|
||||
@@ -73,7 +78,9 @@ struct PositionServiceServer::PImpl {
|
||||
// admin
|
||||
admin_service(context.locate<bts::api::admin::AdminService>()),
|
||||
admin_data(),
|
||||
account_map() {}
|
||||
account_map(),
|
||||
cpp_override_map()
|
||||
{}
|
||||
};
|
||||
|
||||
PositionServiceServer::PositionServiceServer(bts::api::ServiceContext &context,
|
||||
@@ -196,6 +203,27 @@ void PositionServiceServer::_clear_and_publish_pos(){
|
||||
me->pos_publisher.add_or_update(*p.second);
|
||||
me->pos_publisher.flush();
|
||||
}
|
||||
|
||||
void PositionServiceServer::_load_security_params(){
|
||||
me->cpp_override_map.clear();
|
||||
auto all = me->security_params_store->retrieve_all()[security_params_ns];
|
||||
SecurityParameters rec;
|
||||
for (auto const &kvp : all)
|
||||
if (utils::pb::from_string<SecurityParameters>(kvp.second, &rec)){
|
||||
if(rec.has_cpp_override()){
|
||||
LOG(OrderMan, Info, "Overriding cpp, key:{} cpp:{}",
|
||||
rec.security_key(),
|
||||
rec.cpp_override().value());
|
||||
me->cpp_override_map[rec.security_key()] = rec.cpp_override().value();
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG(OrderMan, Error,
|
||||
"Could not recover security parameter for security {} from storage. "
|
||||
"This should be investigated",
|
||||
kvp.first);
|
||||
}
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// Manual Fills API //////////////////////////////////////////////////////////
|
||||
void PositionServiceServer::create_manual_fill(
|
||||
@@ -294,6 +322,30 @@ void PositionServiceServer::set_base_position(
|
||||
_clear_and_publish_pos();
|
||||
}
|
||||
|
||||
void PositionServiceServer::update_security_parameters(
|
||||
const SecurityParametersBatchOp& req,
|
||||
const api::Response<SecurityParametersBatchOpResponse>&){
|
||||
LOG(persist, Info, "PositionServiceServer::update_security_parameters bp count:{}",
|
||||
req.op_params_size());
|
||||
for (const auto& p : req.op_params()){
|
||||
std::string key_str = std::to_string(p.security_key());
|
||||
auto db_p = me->security_params_store->retrieve_one(security_params_ns, key_str);
|
||||
auto found = db_p.first;
|
||||
if (!found) {
|
||||
me->security_params_store->insert(security_params_ns,
|
||||
key_str,
|
||||
utils::pb::to_string(p));
|
||||
}
|
||||
else {
|
||||
me->security_params_store->insert(security_params_ns,
|
||||
key_str,
|
||||
utils::pb::to_string(p));
|
||||
}
|
||||
}
|
||||
_load_security_params();
|
||||
_compute_pnl_and_publish();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// PNL helpers ///////////////////////////////////////////////////////////////
|
||||
void PositionServiceServer::_compute_pnl_and_publish() {
|
||||
@@ -301,7 +353,8 @@ void PositionServiceServer::_compute_pnl_and_publish() {
|
||||
me->state.get_pos(in_pos);
|
||||
std::vector<PNLUnit> pnl;
|
||||
std::vector<AccountPNL> account_pnl;
|
||||
me->pnl_helper.compute_pnl(in_pos, me->account_map, pnl, account_pnl);
|
||||
me->pnl_helper.compute_pnl(in_pos, me->account_map, me->cpp_override_map,
|
||||
pnl, account_pnl);
|
||||
for (const auto &u : pnl)
|
||||
me->pnl_publisher.add_or_update(u);
|
||||
for (const auto &u : account_pnl)
|
||||
|
||||
@@ -52,6 +52,7 @@ class PositionServiceServer : public bts::api::position::PositionServiceBase {
|
||||
void _compute_pnl_and_publish();
|
||||
void _set_admin_data(const api::admin::AdminData &admin_data);
|
||||
void _clear_and_publish_pos();
|
||||
void _load_security_params();
|
||||
public:
|
||||
PositionServiceServer(bts::api::ServiceContext &context, md::MDClient &);
|
||||
|
||||
@@ -74,6 +75,11 @@ class PositionServiceServer : public bts::api::position::PositionServiceBase {
|
||||
const ::bts::api::position::BasePositionBatch &request,
|
||||
const api::Response<::bts::api::position::SetBasePositionResponse>
|
||||
&reply) override;
|
||||
|
||||
void update_security_parameters(
|
||||
const ::bts::api::position::SecurityParametersBatchOp &request,
|
||||
const api::Response<::bts::api::position::SecurityParametersBatchOpResponse>
|
||||
&reply) override;
|
||||
|
||||
~PositionServiceServer();
|
||||
};
|
||||
|
||||
+1
-10
@@ -2,11 +2,6 @@
|
||||
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
|
||||
export PATH="$PATH:/root/.dotnet/tools"
|
||||
|
||||
# install npx
|
||||
if ! [ -x "$(command -v npx)" ]; then
|
||||
npm install -g npx
|
||||
fi
|
||||
|
||||
export OUT=${BUILD_DIR:-../../../build}/web
|
||||
MODE=manual
|
||||
case "$OUT" in
|
||||
@@ -39,9 +34,6 @@ PROTOC=../../../externals/linux/bin/protoc
|
||||
ORANGE='\033[0;33m'
|
||||
NOCOLOR='\033[0m'
|
||||
|
||||
npm install
|
||||
|
||||
|
||||
# Run pbjs to produce protobuf module
|
||||
echo -e \* ${ORANGE} pbjs ${NOCOLOR}
|
||||
npx --no-install pbjs -t json-module -w commonjs -o $OUT/api/proto.js $PROTO_LIST
|
||||
@@ -79,11 +71,10 @@ if [ "$FULL" == "1" ]; then
|
||||
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
|
||||
echo -e \* ${ORANGE} fable ${NOCOLOR}
|
||||
cd ../ui/
|
||||
npm install
|
||||
cd src
|
||||
mkdir -p $OUT/ui/
|
||||
cp -r ../public/* $OUT/ui/
|
||||
dotnet restore
|
||||
dotnet restore -s ../../../../externals/nuget/packages
|
||||
if [ "$MODE" == "release" ]; then
|
||||
dotnet fable webpack -- --env.BUILD_PATH=$OUT -p
|
||||
else
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libbase.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libutils.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libfix_lib.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclxlog.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libade_version.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libade.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclx.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libgmac_version.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libgmac.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libcputimestamp.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libclxconfig.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libconfigfile.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libac.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/librc.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/liblldt.so")
|
||||
LIST(APPEND CELOXICA_LIB "${SPARK_LIB}/celoxica/libregister.so")
|
||||
|
||||
file(COPY ${CELOXICA_LIB} DESTINATION ${CMAKE_BINARY_DIR}/src/server/sparkd)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ if not exist build\src\lib\libspark.vcxproj (
|
||||
)
|
||||
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\Common7\Tools\VsDevCmd.bat"
|
||||
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore spark_client.sln /p:Configuration=release
|
||||
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln /p:Configuration=release
|
||||
IF %ERRORLEVEL% NEQ 0 GOTO :ERROR_AND_END
|
||||
|
||||
msbuild /m /p:debugtype=portable /p:platform=x64 /clp:verbosity=minimal /clp:summary spark_client.sln /p:Configuration=release
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#!/bin/bash
|
||||
BUILD=clang_debug
|
||||
BUILD_LOC=/home/ppinto/temp/spark_build
|
||||
|
||||
if [ -f /.dockerenv ]; then
|
||||
cd $BUILD_LOC/$BUILD && ninja
|
||||
(cd `readlink -f .` && ninja)
|
||||
else
|
||||
cid=`docker ps | grep -P "emacs_ide|bluetradingsystems/linux_docker" | awk '{print $1}'`
|
||||
docker exec $cid /bin/bash -c "cd $BUILD_LOC/$BUILD && ninja"
|
||||
docker exec $cid /bin/bash -c "(cd `readlink -f .` && ninja)"
|
||||
fi
|
||||
@@ -2,6 +2,6 @@
|
||||
setlocal
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\Tools\VsDevCmd.bat" > nul
|
||||
cd /d %~dp0/../
|
||||
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore spark_client.sln
|
||||
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /p:Configuration=release /t:restore spark_client.sln
|
||||
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln
|
||||
msbuild /m /p:platform=x64 /clp:verbosity=minimal /clp:summary /p:Configuration=release /t:restore /p:RestoreSources=externals\windows\nuget\packages spark_client.sln
|
||||
|
||||
@@ -4,6 +4,11 @@ SRC_ROOT=$DIR/../
|
||||
mkdir -p $SRC_ROOT/build/
|
||||
BUILD_ROOT=`readlink -f $SRC_ROOT/build`
|
||||
|
||||
rm -f $SRC_ROOT/src/web/ui/node_modules
|
||||
rm -f $SRC_ROOT/src/web/api/node_modules
|
||||
ln -s $SRC_ROOT/externals/npm/node_modules $SRC_ROOT/src/web/ui/node_modules
|
||||
ln -s $SRC_ROOT/externals/npm/node_modules $SRC_ROOT/src/web/api/node_modules
|
||||
|
||||
rm -rf $BUILD_ROOT/clang_debug
|
||||
mkdir $BUILD_ROOT/clang_debug
|
||||
cd $BUILD_ROOT/clang_debug
|
||||
|
||||
Regular → Executable
File mode changed.
Reference in new issue
Block a user