Compare commits

..
Author SHA1 Message Date
Mohammad Arham 6e942dd2d4 Change: Updated version to v2.2.3 2025-02-05 08:38:31 -05:00
Mohammad Arham 142310a17a Add: SPXW to default for cboe 2025-02-05 12:16:47 +00:00
John Colvin 7f99a89a95 ui (fills): Add notional value column 2025-02-05 00:00:41 -05:00
John Colvin bfce663b4b fix (orderbook): Hook up notional val filter in vm, persist settings 2025-02-04 23:42:58 -05:00
John Colvin 33c743643d fix (orderbook): Include fractional qty in notional val 2025-02-04 23:34:47 -05:00
Taha Afzal 858e0a389b Fixed the issue where a notioanal quoter would send a 0 qty order 2025-02-05 04:16:53 +00:00
Taha Afzal 9c3bb85674 Notional qty handling in spreader 2025-02-05 04:16:53 +00:00
Taha Afzal f4930e8cd4 Added notional to PriceCalc type for the sprader, added code to compute notional books in SpreaderUtils 2025-02-05 04:16:53 +00:00
John Colvin f21e7f3602 ui (orderbook): Show synth notional value in terms of first leg 2025-02-04 16:54:37 -05:00
John Colvin b56c35aca4 ui (ladder): Show synth notional value in terms of first leg 2025-02-04 16:54:37 -05:00
John Colvin e6ac83196e fix (benign): Actually dispose _vm_sub in LadderGridHost 2025-02-04 16:54:37 -05:00
John Colvin 7b6f74fc4a cleanup: Get rid of 'mimic tt' ladder config
This config was leftover from custom DJ work. We no longer need it.
2025-02-04 16:54:37 -05:00
John Colvin f754953fac cleanup: Remove duplicate equality check 2025-02-04 16:54:37 -05:00
Taha 2dd1143d9f Fixed spx spreadbox theos 2025-02-04 16:31:17 -05:00
Mohammad Arham e1ee3275d8 Add: Filter secs upto n months databento 2025-01-31 21:46:11 +00:00
John Colvin 2549e406f3 fix: Compute notional value correctly when ticks per row > 1
It's the sum of price * qty for each level in the row. Previously, we
were multiplying the total qty by the same price (smallest in the row).
2025-01-29 16:28:28 -05:00
John Colvin d5024f1858 fix build: Don't check if unsigned int < 0 2025-01-28 20:20:06 +00:00
John Colvin 3623ac96db fix: Multiply array size by sizeof(T) 2025-01-28 20:19:51 +00:00
John Colvin 19834625f9 fix: Don't fall through switch case 2025-01-28 19:59:32 +00:00
John Colvin c8fc649b6b fix build: Pass config arg to cmake --build
For Ninja Multi-Config builds (i.e., api server and data streams)
2025-01-28 19:59:09 +00:00
John Colvin 6b1fc1c413 cleanup: Remove unused code 2025-01-28 19:59:09 +00:00
John Colvin 6f086532ac cleanup: Convert co2::TradeMessage in DomainSecurityUtils
Instead of domain::TradeRecord constructor. The domain types should have
minimal dependency on protos.
2025-01-28 19:59:09 +00:00
John Colvin dc66282225 cleanup: Rename UINT32 -> VALUE in qty macro names
Should have done this when we changed qty primitive from uint32 to float
2025-01-28 19:59:09 +00:00
John Colvin e2c74c0bb3 cleanup: Don't redefine CONTAINS macro 2025-01-28 19:59:09 +00:00
John Colvin e89d50eec1 cleanup: Move large fns from header to cpp 2025-01-28 19:59:08 +00:00
John Colvin 8bebbc278d cleanup: Get rid of 'using namespaces'
In headers, this is an objectively bad practice, since the type aliases
leak into dependent code and can cause unexpected naming conflicts. In
source files, it is debatable, but I prefer explicit aliases of the form
'using Foo = Bar'. The intentions are clear, and the resulting code is
naturally resistent to naming conflicts.
2025-01-28 19:59:08 +00:00
John Colvin 2e6d12c9c1 cleanup: Remove unnecessary includes 2025-01-28 19:59:08 +00:00
John Colvin 102a5e23ea vscode: Add Tests Sparkd entries to tasks.json 2025-01-28 19:59:08 +00:00
John Colvin 582dc791db vscode: Add Run Sparkd (Skip Build) entry to tasks.json 2025-01-28 19:59:08 +00:00
John Colvin afb35d8c25 Compute synth book qty in terms of first leg 2025-01-28 19:59:08 +00:00
John Colvin 3a32ed5fe6 Fix BookSide equality operators 2025-01-27 16:35:22 +00:00
John Colvin 4e01697327 Add equality and output operators to BookLevel 2025-01-27 16:25:32 +00:00
John Colvin 22032c1742 Add fmt::formatter for domain::Side
So we can pass it to LOG macros, etc.
2025-01-27 16:25:15 +00:00
John Colvin 0a9bce317c Add contract qty info to security proto (and FixedSecurity)
We should get this kind of security metadata from the exchange rather
than config.
2025-01-25 20:41:47 +00:00
Mohammad Arham 40e44db597 Fix: Added GCHandle in attempt to fix ui crash 2025-01-22 02:15:55 -05:00
Mohammad Arham 4a447e44e1 Add: Version to first log 2025-01-22 02:14:19 -05:00
John Colvin b1f4654b3c Fix benign typo in publish_spark.py 2025-01-21 17:28:50 -05:00
John Colvin 9d350334c0 fix build (installer): Fix install path typo in publish_spark.py 2025-01-21 17:28:05 -05:00
John Colvin ac597f505f fix build (installer): Set CMAKE_INSTALL_PATH for spark preset 2025-01-21 17:19:15 -05:00
John Colvin 147d9ceb1f ui: Enable net change price display for synths in ladder 2025-01-21 13:11:16 -05:00
John Colvin 377804dfb9 fix (ui): Don't crash on bad regex in grid filters
Wrap Regex construction in try/catch. This is from user input, so of
course we can't assume it will be valid.
2025-01-21 09:26:37 -05:00
John Colvin 0d26401961 fix: Preserve tick_factors in TickDisplaySettings "with" ext
Not sure this was a bug in practice, but it was definitely wrong.
2025-01-21 01:24:23 -05:00
John Colvin 889e226151 feat: Add setting to show notional values in ladder 2025-01-21 01:24:23 -05:00
John Colvin 986d53f4e0 ui: Make ladder size format text editable
This is the best of both worlds: the user can choose from one of the
canned presets in the dropdown, or specify a custom string format.
2025-01-21 01:24:23 -05:00
Taha d13ba2968a DateTimeExpanstionStream col name changes 2025-01-20 15:34:11 -05:00
John Colvin 1a192c8117 feat: Add setting to show ladder prices in basis pts from mid 2025-01-20 14:15:49 -05:00
John Colvin 5e36d4801c cleanup: Remove redundant if/else code 2025-01-20 13:31:41 -05:00
John Colvin 2cf6411ca7 fix: Send exchangeInfo req via our HttpClient instead of ccapi
Bugs (fixed):
  * We get a boost beast exception about the read limit being exceeded
    when requesting securities from Binance prod url (testnet works).
2025-01-19 14:21:11 +00:00
John Colvin e99df129d5 cleanup: Remove erroneous include 2025-01-18 22:54:06 +00:00
John Colvin d07c01bd17 ui: Support ladder ticks-per-row setting for synths
The ticks-per-row control was likely hidden by accident in the first
place. I'm guessing the original intent was to only hide the Price Terms
control for synthetics. At the time, there were no other controls in the
group box, so it made since to hide the whole thing. Some time later, we
added the ticks-per-row setting to the same group box, and forgot to
make the groupbox visible again.
2025-01-17 13:11:09 -05:00
Taha Afzal d69673c524 Previous change was not setting the create time for orders, fixing that + changign one log level to Dag 2025-01-17 07:00:57 +00:00
Taha Afzal 4ee595cdaf Fixing the issue were fractional fills on a working order could result in overfills being reported (it was only a reporting issue) 2025-01-17 06:38:38 +00:00
Mohammad Arham 6fcaf07df0 Fix: binance execution working on sim 2025-01-17 00:59:22 +00:00
Taha Afzal 492d48d886 Added qty_multiplier to spreader.proto 2025-01-16 20:18:26 +00:00
Taha Afzal b6a56e768b Adding config boolan config publish_boostrap_books_only to MarketDataReplayConfig which allows MDReplayer to only publish the bootstrap of all the books 2025-01-16 20:18:26 +00:00
Taha Afzal 3e2b5976a8 Minor changes and ocmments, to allow for fractional qtys 2025-01-16 20:18:26 +00:00
Mohammad Arham edd3f6cbd6 FIX: Orderbook quantity display fixed 2025-01-15 14:47:09 -05:00
John Colvin 2e9347341b fix: Don't throw ex just because leg qty is 0
ContractLegEnumerable shouldn't be responsible for security validation.
2025-01-14 12:58:58 -05:00
John Colvin 2061e38349 fix: Hook up mview venue filter in vm 2025-01-13 00:11:43 -05:00
Mohammad Arham 650dd57b6d FIX: increase max symbo count from 32 to 1024 2025-01-13 04:42:13 +00:00
Mohammad Arham 82747f7dc0 Fix: OKX perp swaps are same as futures now 2025-01-13 04:41:29 +00:00
John Colvin 04b26904a3 ui: Sort exchanges in grid filter dropdown 2025-01-12 15:10:20 -05:00
John Colvin f123e4c72c ui: Add Exchange col to mview 2025-01-12 15:10:20 -05:00
John Colvin b887cbebab fix: Actually capture emumba deribit cfg obj by value
The previous "fix" didn't actually work because, although the lambda
captured the obj by value, the thread captured the lambda by reference!
2025-01-09 16:53:22 +00:00
John Colvin 14e975bc7e vscode: Add Spark (Skip Build) entry to tasks.json 2025-01-09 11:23:30 -05:00
John Colvin 064f557057 ui: Sort accounts in ladder/ticket dropdown 2025-01-09 11:23:30 -05:00
John Colvin 4d106b5e6c ui: Fix alignment issues in order ticket 2025-01-09 11:23:30 -05:00
John Colvin 9fbeaf0d68 ui: Make TickUpDown style match default (dark) Button
* Set BorderThickness to 0
* Use same mouse-hover background as Button
2025-01-09 11:23:30 -05:00
John Colvin 4018ac8b78 ui: Add tooltips to order ticket 2025-01-09 11:23:30 -05:00
John Colvin 81c9503300 ui: Apply dark theme to order ticket buttons 2025-01-09 11:23:30 -05:00
John Colvin 477210926d ui: Fix text color in algo popup 2025-01-09 11:23:29 -05:00
John Colvin 0aa48a79a9 ui: Remove single quotes from tooltip
It looked busy
2025-01-09 01:38:26 -05:00
John Colvin 9e71c7af02 ui: Add tooltips to ladder 2025-01-09 01:38:26 -05:00
John Colvin 4647751409 vscode: Add Spark Test entry to launch.json, tasks.json 2025-01-09 01:03:33 -05:00
John Colvin a3087b874b cleanup: Remove unused code 2025-01-09 01:03:32 -05:00
John Colvin 5b2f5c5c8f Don't throw/catch type exception needlessly 2025-01-09 01:03:32 -05:00
John Colvin 479227090e ui: Remove overly rigid validation for "ticks per row" setting 2025-01-09 01:03:32 -05:00
John Colvin 2dfcf320f5 fix: Refresh ladder levels when "ticks per row" changes 2025-01-09 01:03:32 -05:00
John Colvin b4c7213cb2 fix: Refresh ladder prices in view when they change
Bugs (fixed):
  * Sometimes the prices in the left panel of the ladder get stale
2025-01-09 01:03:32 -05:00
John Colvin c7dfd69b90 Consolidate tick rounding functions
Too many functions were performing essentially the same service:
rounding ticks. It was confusing.
2025-01-09 01:03:32 -05:00
John Colvin 4fdc866485 build: Propagate py script exit codes back to the shell
I noticed the build cmds were broken on Windows because the debugger
would still start even when the preLaunchTask (build) failed!
2025-01-09 01:03:32 -05:00
John Colvin f5d5ca9a1c ui: Use appropriate colors in font picker control
We must have missed this when we switched to dark theme.
2025-01-09 01:03:32 -05:00
John Colvin cfafcf77b9 ui: Use appropriate text color in ladder settings dialog
We must have missed this black text when we switched to dark theme.
While we're at it, freeze the brushes it uses, since they are stored in
a StaticResource which could potentially be shared by multiple controls.
2025-01-09 01:03:32 -05:00
John Colvin 8ebe783139 fix: Clear low/high lines in ladder when scrolled out of view 2025-01-09 01:03:32 -05:00
John Colvin 1851a96a58 vscode: Add Spark (Skip Build) entry to launch.json 2025-01-09 01:03:32 -05:00
John Colvin 0b334ef870 fix: Remove erroneous early exit condition in Ladder render
Bugs (fixed):
  * The ladder doesn't always render ask prices
2025-01-09 01:03:32 -05:00
John Colvin ee811a729a ui: Make ladder price line colors match button text 2025-01-09 01:03:32 -05:00
John Colvin cb15be276d ui: Show bid, ask, mid lines in ladder by default 2025-01-09 01:03:32 -05:00
John Colvin 7d5af6729b ui: Show bid, ask, mid prices in ladder left panel 2025-01-09 01:03:31 -05:00
John Colvin e5679cf082 ui: Make ladder price links prettier
* Change them from hyperlinks to buttons
* Add mouse hover animation
* Align text to the right (so decimal places line up)
2025-01-09 01:03:31 -05:00
John Colvin 98f89b9ad4 fix: Capture emumba deribit cfg by value in thread body
Bugs (fixed):
  * Sparkd occasionally assert crashes on startup because it fails to
    read emumba deribit config.

    Cause: This is a simple race bug. The config obj isn't captured by
    value, so it can be destructed before the thread references it.
2025-01-08 19:03:01 +00:00
262 changed files with 7897 additions and 12419 deletions

No files matched your search

+65
View File
@@ -55,6 +55,21 @@
"externalConsole": false,
"MIMode": "gdb",
},
{
"name": "spreader tests",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/sparkd-debug-clang/server/sparkd",
"args": [
"test",
"[spreader]"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/sparkd-debug-clang/server",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
},
{
"name": "DataStreamQueryAnalyzer",
"type": "coreclr",
@@ -108,6 +123,18 @@
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
"name": "Spark (Skip Build)",
"type": "coreclr",
"request": "launch",
"program": "",
"windows": {
"program": "Spark.exe",
"cwd": "build/Debug/bin/net7.0-windows",
},
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
"name": "Spark (Parameterized)",
"type": "coreclr",
@@ -125,6 +152,39 @@
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
"name": "Spark Test",
"type": "coreclr",
"request": "launch",
"program": "",
"windows": {
"preLaunchTask": "Build Spark (Debug)",
"program": "Spark.Console.exe",
"cwd": "build/Debug/bin/net7.0",
"args": [
"test",
"${input:sparkTest}"
]
},
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
"name": "Spark Test (Skip Build)",
"type": "coreclr",
"request": "launch",
"program": "",
"windows": {
"program": "Spark.Console.exe",
"cwd": "build/Debug/bin/net7.0",
"args": [
"test",
"${input:sparkTest}"
]
},
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
"name": "Sparkd",
"type": "cppdbg",
@@ -255,6 +315,11 @@
"description": "Config Server URL",
"default": "http://localhost:8000/config",
},
{
"id": "sparkTest",
"type": "promptString",
"description": "Test to run",
},
{
"id": "sparkdTest",
"type": "promptString",
+103 -1
View File
@@ -641,6 +641,21 @@
"color": "terminal.ansiGreen",
}
},
{
"label": "Run Spark (Skip Build)",
"type": "process",
"command": "",
"windows": {
"command": "Spark.exe",
"options": {
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net7.0-windows"
},
},
"icon": {
"id": "run",
"color": "terminal.ansiGreen",
}
},
{
"label": "Run Spark Config Query",
"dependsOn": "Build Spark",
@@ -684,6 +699,24 @@
"color": "terminal.ansiGreen",
},
},
{
"label": "Run Sparkd (Skip Build)",
"type": "process",
"command": "",
"linux": {
"command": "sparkd",
},
"args": [
"run"
],
"options": {
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
},
"icon": {
"id": "run",
"color": "terminal.ansiGreen",
},
},
{
"label": "Run Sparkd Config Query",
"dependsOn": "Build Sparkd",
@@ -705,6 +738,65 @@
"color": "terminal.ansiGreen",
},
},
{
"label": "Test Spark (Debug)",
"dependsOn": "Build Spark (Debug)",
"type": "process",
"icon": {
"id": "coverage",
"color": "terminal.ansiYellow",
},
"command": "",
"windows": {
"command": "Spark.Console.exe",
"args": [
"test",
"${input:sparkTest}"
],
"options": {
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0",
}
},
},
{
"label": "Test Sparkd",
"dependsOn": "Build Sparkd",
"type": "process",
"command": "",
"linux": {
"command": "sparkd",
},
"args": [
"test",
"${input:sparkdTest}",
],
"options": {
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
},
"icon": {
"id": "coverage",
"color": "terminal.ansiYellow",
},
},
{
"label": "Test Sparkd (Skip Build)",
"type": "process",
"command": "",
"linux": {
"command": "sparkd",
},
"args": [
"test",
"${input:sparkdTest}",
],
"options": {
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
},
"icon": {
"id": "coverage",
"color": "terminal.ansiYellow",
},
},
{
"label": "View DataStreamQueryAnalyzer Logs (Last Run)",
"type": "process",
@@ -823,6 +915,16 @@
"id": "sparkdConfigQuery",
"type": "promptString",
"description": "Config Type(s)"
}
},
{
"id": "sparkTest",
"type": "promptString",
"description": "Test to run",
},
{
"id": "sparkdTest",
"type": "promptString",
"description": "Test to run",
},
]
}
-1
View File
@@ -117,7 +117,6 @@ else()
# subdirs
if (BUILD_SPARKD)
add_subdirectory(src/lib_binance_execution)
add_subdirectory(src/lib_deribit_emumba)
add_subdirectory(src/server/sparkd)
add_subdirectory(src/data_files)
endif()
+1
View File
@@ -26,6 +26,7 @@
"CMAKE_CXX_FLAGS_DEBUG": "$env{SPARK_CMAKE_CXX_FLAGS} /MTd",
"CMAKE_CXX_FLAGS_RELEASE": "$env{SPARK_CMAKE_CXX_FLAGS} /MT",
"CMAKE_CXX_STANDARD": "20",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
"CMAKE_SHARED_LINKER_FLAGS_DEBUG": "/DEBUG",
"CMAKE_SHARED_LINKER_FLAGS_RELEASE": "/OPT:REF /OPT:ICF /LTCG",
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
@@ -1,25 +0,0 @@
include(ExternalProject)
if(NOT TARGET websocket-library_project)
ExternalProject_Add(websocket-library_project
GIT_REPOSITORY https://fintech.websocket:h_ubh-4dsWRB569wwKyg@gitlab.com/cryptoexchanges1/websocket-client-using-epoll.git
GIT_TAG enhancement/config-ssl
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/websocket-library
-DCMAKE_BUILD_TYPE=Release
-DLOG_LEVEL=INFO
-DENABLE_ENCRYPTION=True
)
endif()
ExternalProject_Get_Property(websocket-library_project BINARY_DIR)
ExternalProject_Get_Property(websocket-library_project SOURCE_DIR)
set(websocket-library_INCLUDE_DIR ${SOURCE_DIR}/include)
set(websocket-library_LIB_DIR ${BINARY_DIR}/src)
add_library(websocket-library STATIC IMPORTED)
set_property(TARGET websocket-library PROPERTY IMPORTED_LOCATION ${websocket-library_LIB_DIR}/libemumba-websocket-library.a)
add_dependencies(websocket-library websocket-library_project)
install(FILES ${websocket-library_LIB_DIR}/libemumba-websocket-library.a DESTINATION ${CMAKE_INSTALL_LIBDIR})
@@ -4,7 +4,7 @@ namespace bts.SilverPine {
using BCG;
using System;
[DataStreamUserRespresentation("DateTimeExpanderStream")]
public class DateTimeExpanstionStream : ViewStream {
public DateTimeExpanstionStream(ViewStream parent_stream, string inp_col) {
Func<IBlueTableSchema, IBlueTableSchema> get_schema = (s) => {
@@ -24,11 +24,11 @@ namespace bts.SilverPine {
IBlueTableSchema s = get_schema(r.schema);
MutableBlueRow rv = new MutableBlueRow(s, r);
rv.setValue($"{inp_col}_date", dt.Date);
rv.setValue($"{inp_col}_day", dt.DayOfWeek.ToString());
rv.setValue($"{inp_col}_day_of_week", dt.DayOfWeek.ToString());
rv.setValue($"{inp_col}_day_of_month", dt.Day);
rv.setValue($"{inp_col}_month", dt.Date.Month);
rv.setValue($"{inp_col}_hour", dt.Hour);
rv.setValue($"{inp_col}_min", dt.Minute);
rv.setValue($"{inp_col}_minute", dt.Minute);
rv.setValue($"{inp_col}_seconds_of_day", (dt - dt.Date).TotalSeconds);
return rv;
});
+6
View File
@@ -36,6 +36,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnExpressions", "Bt
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnSortExpressions", "Bts.ColumnSortExpressions\Bts.ColumnSortExpressions.csproj", "{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStreamQueryAnalyzer", "DataStreamQueryAnalyzer\DataStreamQueryAnalyzer.csproj", "{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -113,5 +115,9 @@ Global
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Release|Any CPU.Build.0 = Release|Any CPU
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
@@ -156,6 +156,14 @@ namespace bts.SilverPine {
ViewStream rs = new FutureHistoricalBarsStream(fut_contract, bar_size.ToString(), dt_range_beg, dt_range_end);
mySetRealSource(rs);
}
public FutureHistoricalBarsStream(string fut_contract, string bar_size)
: this(fut_contract, bar_size, DateTime.MinValue, DateTime.MaxValue) { }
public FutureHistoricalBarsStream(string fut_contract, string bar_size,
string dt_range_beg, string dt_range_end)
: this(fut_contract, bar_size, DateTime.Parse(dt_range_beg),
DateTime.Parse(dt_range_end)) { }
public FutureHistoricalBarsStream(string fut_contract, string bar_size,
DateTime dt_range_beg, DateTime dt_range_end)
:
@@ -13,7 +13,7 @@ namespace mbts.analytics {
// prices (specified in the vs settings).
// It has public setters that get
// 1. contracts
// 2. vs inout
// 2. vs input
// 3. exp params(drivers, interst rate etc)
// 4. driver prices
// 5. vol time
@@ -1,6 +1,7 @@
namespace mbts.spark {
public enum MarketViewColumnType {
None,
Venue,
Security,
Volume,
Last,
@@ -1,4 +1,6 @@
namespace bts.shell.market_view {
using bts.api;
using mbts.spark;
using mbts.spark.filters;
@@ -8,6 +10,8 @@ namespace bts.shell.market_view {
[ProtoContract]
class MarketViewFilterStates {
[ProtoMember(37)]
public Venue[] venue { get; set; }
[ProtoMember(1)]
public string security { get; set; }
[ProtoMember(2)]
@@ -123,6 +127,7 @@ namespace bts.shell.market_view {
public Func<MarketViewVM.Row, bool> build_predicate() {
var pb = new ColumnPredicateBuilder<MarketViewVM.Row>();
// security
pb.add_discrete_values(venue, r => r.venue);
pb.add_regex(security, r => r.security.ToString());
pb.add_regex(security_key, r => r.security_key.ToString());
@@ -206,6 +206,7 @@
// security
public SecurityMD security_md { get; }
public SecurityImmutable security => security_md.security;
public bts.api.Venue venue => security_md.security.Exchange;
public uint security_key => security_md.security.Id;
public string currency => security_md.security.Currency;
@@ -92,6 +92,7 @@ namespace bts.shell.market_view {
}
static class DefaultColumns {
public static GridColumnSettings venue => _mk(Columns.Venue, ColHeader.Exchange, ColWidth.Exchange, h_align: HorizontalAlignment.Left);
public static GridColumnSettings security => _mk(Columns.Security, ColHeader.Security, ColWidth.Security, h_align: HorizontalAlignment.Left);
public static GridColumnSettings currency => _mk(Columns.Currency, ColHeader.Currency, ColWidth.Currency, h_align: HorizontalAlignment.Left, is_visible: false);
public static GridColumnSettings volume => _mk(Columns.Volume, ColHeader.Volume, ColWidth.Volume, h_align: HorizontalAlignment.Left);
@@ -122,6 +122,7 @@ namespace mbts.spark {
const double working_orders_col_width = 105;
var price_converter = new TickDisplayConverter();
add_col(ColKey.Venue, r => r.venue.ToString(), nameof(Row.venue), venue_filter, FilterType.DiscreteValues, ColWidth.Exchange);
add_col(ColKey.Security, r => r.security.ToString(), nameof(Row.security), security_filter, FilterType.Regex, ColWidth.Security);
add_col(ColKey.Currency, r => r.currency, nameof(Row.currency), currency_filter, FilterType.Regex, ColWidth.Currency);
add_col(ColKey.Volume, r => r.volume_text, nameof(Row.volume), volume_filter, FilterType.ULongMinMax, ColWidth.Volume);
@@ -388,6 +389,7 @@ namespace mbts.spark {
internal IBus bus => _bus;
internal OrdersModel orders_model => _orders_model;
public IDataGridColumnPersistentFilter<Row, Venue[]> venue_filter { get; } = DiscreteValuesFilterVM.Common.exchange<Row>(r => r.venue, nameof(Row.venue));
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
public IDataGridColumnPersistentFilter<Row, string> currency_filter { get; }
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
@@ -535,6 +537,7 @@ namespace mbts.spark {
public MarketViewFilterStates get_filter_states() =>
new() {
venue = venue_filter.get_state(),
security = security_filter.get_state(),
bid_price = bid_price_filter.get_state(),
bid_merged_size = bid_qty_filter.get_state(),
@@ -580,6 +583,7 @@ namespace mbts.spark {
}
using var _ = rows_view.DeferRefresh();
venue_filter.load_state(filters.venue);
security_filter.load_state(filters.security);
bid_price_filter.load_state(filters.bid_price);
bid_qty_filter.load_state(filters.bid_merged_size);
@@ -237,6 +237,17 @@ namespace mbts.spark {
DefaultColumnWidths.Qty,
get_fg: r => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
add_col(
Columns.NotionalValue,
DefaultColumnHeaders.NotionalValue,
r => _get_row(r).security.spec_or_default().format(r.fill.notional_value()),
new DoubleMinMax<FillTuple>(r => r.fill.notional_value()),
DataGridFilterKnownTemplates.DoubleMinMax,
fs => fs.notional_value,
(fs, cf) => fs.notional_value = cf,
DefaultColumnWidths.NotionalValue,
get_fg: r => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
add_col(
Columns.Price,
DefaultColumnHeaders.Price,
@@ -21,6 +21,7 @@ namespace mbts.ui.spark {
Side,
Price,
Size,
NotionalValue,
FillKind,
FillStatus,
FillKey,
@@ -61,6 +61,10 @@ namespace bts.shell.fills {
[DIYGridColumnID(nameof(Columns.Size))]
public DoubleMinMaxState size { get; set; }
[ProtoMember(18)]
[DIYGridColumnID(nameof(Columns.NotionalValue))]
public DoubleMinMaxState notional_value { get; set; }
[ProtoMember(11)]
[DIYGridColumnID(nameof(Columns.FillKey))]
public string fill_key { get; set; }
@@ -113,6 +117,7 @@ namespace bts.shell.fills {
pb.add_discrete_values(fs.fill_status, ft => ft.fill.FillStatus);
// numeric
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
pb.add_numeric(fs.notional_value, ft => ft.fill.notional_value());
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
if (fs.exp_date.HasValue) {
File diff suppressed because it is too large. Load diff
@@ -66,8 +66,12 @@ namespace mbts.spark {
_orders = orders;
_real_price = real_price;
_text_lines = backing(nameof(text_lines), () => {
var fmt = MDDisplayHelper.get_value_formatter(_owner.model.security_md.quantity_display);
return BadgeRender.format(side, _quantity, _filled, _current_badge_style, fmt);
var tf = _owner.model.security.tick_factors();
var fmt = MDDisplayHelper.get_value_formatter(
owner.current_settings.size_display,
_owner.model.security_md.quantity_display,
tf.to_api_tick_display_settingsi());
return BadgeRender.format(side, _quantity, _filled, price, _current_badge_style, fmt);
});
_clear_phantom_price = new(() => {
_phantom_price = double.NaN;
@@ -100,6 +100,9 @@ namespace mbts.spark {
readonly OrderEntryPanelItemVisibilityVM _cancel_all_visibility = new("Cancel All");
readonly OrderEntryPanelItemVisibilityVM _status_message_visibility = new("Status Message");
readonly PanelPricePointVM _bid_price = new("Bid");
readonly PanelPricePointVM _ask_price = new("Ask");
readonly PanelPricePointVM _mid_price = new("Mid");
readonly PanelRelativePricePointVM _day_change = new("Day Change");
readonly PanelPricePointVM _session_high = new("High");
readonly PanelPricePointVM _session_low = new("Low");
@@ -139,9 +142,6 @@ namespace mbts.spark {
accounts_view = new ListCollectionView<AccountTuple?>(_accounts) {
CustomSort = NullCheckComparer<AccountTuple>.Default
};
// cmds
tick_up_ticks_per_row_cmd = new DelegateCommand(increment_ticks_per_row);
tick_down_ticks_per_row_cmd = new DelegateCommand(decrement_ticks_per_row);
// update
_update = new DeferrableAction(() => result_changed?.Invoke());
mini_ob.result_changed += _update.Invoke;
@@ -191,26 +191,6 @@ namespace mbts.spark {
}
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data);
void increment_ticks_per_row() {
var tpr = ticks_per_row;
for (uint i = 1; i < 100; i++) {
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security, ticks_per_row + i);
if (new_tpr != tpr) {
ticks_per_row = new_tpr;
break;
}
}
}
void decrement_ticks_per_row() {
var tpr = ticks_per_row;
for (uint i = 1; i < tpr; i++) {
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security, tpr - i);
if (new_tpr != tpr) {
ticks_per_row = new_tpr;
break;
}
}
}
}
public event Action result_changed;
@@ -235,15 +215,8 @@ namespace mbts.spark {
}
public uint ticks_per_row {
get => _ticks_per_row;
set => Set(ref _ticks_per_row, value, dependent_props: nameof(rounded_ticks_per_row));
set => Set(ref _ticks_per_row, value);
}
public ICommand tick_up_ticks_per_row_cmd { get; }
public ICommand tick_down_ticks_per_row_cmd { get; }
// since settings profiles may be shared among ladders
// of different securities/tick sizes, the ticks per row setting
// may need to be rounded to a number that is a factor of $1.00
public uint? rounded_ticks_per_row =>
TickUtils.fit_ticks_per_row_to_dollars(_security, ticks_per_row);
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
@@ -327,7 +300,12 @@ namespace mbts.spark {
public IEnumerable order_entry_panel_items {
get {
// Order these items according to their display order in the actual ladder (from top to
// bottom). There are a lot of them, so this is a simple convenience for the user.
yield return _zoom_slider_visbility;
yield return _ask_price;
yield return _mid_price;
yield return _bid_price;
yield return _day_change;
yield return _session_high;
yield return _session_low;
@@ -450,6 +428,12 @@ namespace mbts.spark {
zoom_factor = settings.zoom_factor;
// left-panel (show/hide)
_zoom_slider_visbility.show = settings.zoom_slider_visible;
_bid_price.show = settings.bid_price_visible;
_bid_price.load_from(settings.panel_price_point_settings.bid_price);
_ask_price.show = settings.ask_price_visible;
_ask_price.load_from(settings.panel_price_point_settings.ask_price);
_mid_price.show = settings.mid_price_visible;
_mid_price.load_from(settings.panel_price_point_settings.mid_price);
_day_change.show = settings.day_visible;
_day_change.load_from(settings.panel_price_point_settings.day_change);
_session_high.show = settings.high_visible;
@@ -537,6 +521,9 @@ namespace mbts.spark {
.ToList();
// left-panel price items
clone.panel_price_point_settings = new LadderPanelPricePointSettings {
bid_price = _bid_price.create(),
ask_price = _ask_price.create(),
mid_price = _mid_price.create(),
day_change = _day_change.create(),
open = _open.create(),
session_low = _session_low.create(),
@@ -561,6 +548,9 @@ namespace mbts.spark {
clone.order_entry_panel_initial_width = _order_entry_panel_width;
clone.zoom_factor = zoom_factor;
// left-panel (components)
clone.bid_price_visible = _bid_price.show;
clone.ask_price_visible = _ask_price.show;
clone.mid_price_visible = _mid_price.show;
clone.day_visible = _day_change.show;
clone.high_visible = _session_high.show;
clone.low_visible = _session_low.show;
@@ -715,6 +705,7 @@ namespace mbts.spark {
public uint sample_quantity { get; } = 50;
public uint sample_filled { get; } = 12;
public double sample_price => 2175.50;
public Side sample_side { get; } = Side.Buy;
public Brush badge_border { get; } = new SolidColorBrush(BTSColors.MarketData.ask);
public Brush badge_background { get; } = Brushes.White;
File diff suppressed because it is too large. Load diff
@@ -14,9 +14,9 @@ using mbts.ui.utility.text;
using mbts.utils;
using Spark.Core.Disposables;
using Spark.Core.Math;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@@ -37,9 +37,6 @@ namespace mbts.spark {
partial class LadderGridHost : Control {
static readonly int _initial_row_count;
static readonly bool _show_scrollbar;
static readonly bool _lighten_market_cells;
static readonly Color? _bid_market_cell_background;
static readonly Color? _ask_market_cell_background;
readonly VisualHost _visual_host = new();
readonly LadderOrdersHost _ordershost;
@@ -83,6 +80,7 @@ namespace mbts.spark {
LadderTactileAdorner _tactile_adorner;
ulong _prices_updated;
NativePriceSize[] _bids = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH], _asks = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
SecurityMDExtensions.Book _depth = SecurityMDExtensions.Book.Empty;
bool _price_dirty = false, _piq_dirty = false;
LadderPriceList _price_list = new() {
length = _initial_row_count,
@@ -93,8 +91,10 @@ namespace mbts.spark {
readonly MarketBreakLines _mkt_break_lines;
NativePriceSize _last_trade;
Func<NativePriceSize, string> _fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi);
Func<double, string> _fmt_mkt_quantity_impl = v => v.ToString();
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
Func<double, double, string> _fmt_mkt_quantity_impl = (v, p) => v.ToString();
Func<LadderPriceLevel, string> _fmt_price_level_impl = MDDisplayHelper.get_formatter_lpl(
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
int _old_tooltip_row = int.MaxValue;
NativePriceSize? _old_last_trade;
string _direction_arrow = string.Empty;
@@ -106,17 +106,7 @@ namespace mbts.spark {
static LadderGridHost() {
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
_show_scrollbar = knobs.ShowScrollbar;
_lighten_market_cells = knobs.LightenMarketCells;
_initial_row_count = (int)knobs.ScrollableTickWindowSize;
// For market cell backgrounds, 0 means null. Note that the full
// range of colors can still be expressed, as zero is merely one of
// 2^24 possible representations of 'transparent.'
if (knobs.BidMarketCellBackground != 0) {
_bid_market_cell_background = BTSColors.Ladder.bid_column_background;
}
if (knobs.AskMarketCellBackground != 0) {
_ask_market_cell_background = BTSColors.Ladder.ask_column_background;
}
}
public LadderGridHost() {
@@ -736,8 +726,8 @@ namespace mbts.spark {
};
view.RequestClose += (o, _) => popup.IsOpen = false;
}
string fmt_mbo_qty(int value) =>
value < 0 ? $"-{_fmt_mkt_quantity((uint)value)}" : _fmt_mkt_quantity((uint)value);
string fmt_mbo_qty(double value, double price) =>
value < 0 ? $"-{_fmt_mkt_quantity(value, price)}" : _fmt_mkt_quantity(value, price);
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
@@ -871,32 +861,13 @@ namespace mbts.spark {
switch (column.column_type) {
case ColumnType.Order: _ordershost.set_col_width(column.settings.width); break;
case ColumnType.Bid:
render_market_col(_bids, _bid_market_cell_background);
render_market_col(_depth.Bids);
break;
case ColumnType.Price:
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
LadderRender.render_prices(
column.visual_tup.visual,
column.visual_tup.glyph_context,
_price_list,
_settings.row_height,
column.settings,
_spec,
_vm.md.settlement_price,
_settings.net_change == LadderSettings.NetChangeTerms.Ticks);
} else {
LadderRender.render_prices(
column.visual_tup.visual,
column.visual_tup.glyph_context,
_price_list,
_settings.row_height,
column.settings,
_spec,
_settings.price_display_format);
}
_refresh_prices(column);
break;
case ColumnType.Ask:
render_market_col(_asks, _ask_market_cell_background);
render_market_col(_depth.Asks);
break;
case ColumnType.Volume:
_volume_container.mark_dirty();
@@ -909,9 +880,9 @@ namespace mbts.spark {
if (_mkt_break_lines.high_render_index.HasValue)
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index.Value, _settings.session_high_line.color);
}
void render_market_col(NativePriceSize[] depth, Color? market_cell_background) {
void render_market_col(IEnumerable<(double Price, double Volume, double NotionalValue)> depth) {
var mid = LadderRender.index_of(_price_list, _last_origin_price, _price_comparer);
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_mkt_depth_item, _lighten_market_cells, market_cell_background);
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_price_level);
}
}
_apply_column_visual_offset();
@@ -957,6 +928,7 @@ namespace mbts.spark {
// **use local vm to protect against re-assignment,
// so the events are unhooked from the same instance to which they were added
// when the subs are disposed.
Disposable.dispose(ref _vm_sub);
var local_vm = _vm;
local_vm.on_tick += _tick;
local_vm.request_recenter += on_request_recenter;
@@ -1015,8 +987,10 @@ namespace mbts.spark {
//System.Diagnostics.Debug.WriteLine(" Tooltip - Previous tooltip is visible, but we've changed rows");
_hide_tool_tip();
}
_tooltip_view.Content = _fmt_mkt_quantity(volume);
double price = row < 0 || row >= (_price_list?.Count ?? 0)
? double.NaN
: _price_for_row(row);
_tooltip_view.Content = _fmt_mkt_quantity(volume, price);
_tooltip_view.IsOpen = true;
ToolTip = _tooltip_view;
} else {
@@ -1103,19 +1077,34 @@ namespace mbts.spark {
return Disposable.Create(() => _can_user_send_orders_changed -= o.OnNext);
});
LadderPriceList _create_price_list() {
if (_settings is not null) {
switch (_settings.net_change) {
case LadderSettings.NetChangeTerms.BasisPoints:
case LadderSettings.NetChangeTerms.NetBasisPoints:
return new() {
length = _initial_row_count,
regional_tick = new RegionalTickFactors(new user.settings.TickFactors(10000, 1)),
};
}
}
return new() {
length = _initial_row_count,
regional_tick = _spec
};
}
#region Timer.Tick, Refresh Methods
void _tick() {
if (_settings != null) {
ulong unused = 0;
bool last_trade_changed = _vm.md.get_last_trade(ref unused, out _last_trade, out DateTime _, out ulong __);
bool depth_changed = _vm.md.get_depth(ref _prices_updated, ref _bids, ref _asks, NativePriceHelpers.COCONUT_MAX_DEPTH);
_depth = _vm.security_md.Depth(_bids, _asks, _vm.securities);
_mkt_break_lines.refresh();
if (_price_list is null) {
Log.assert(_price_dirty);
_price_list = new LadderPriceList {
length = _initial_row_count,
security = security
};
_price_list = _create_price_list();
var mid_tick = security.tickdiff(_mkt_break_lines.origin, 0) ?? 0;
if (mid_tick > int.MaxValue) {
Log.Ui.error(
@@ -1152,10 +1141,8 @@ namespace mbts.spark {
_scroll_viewer.ScrollToVerticalOffset(new_vertical_offset);
}
}
_refresh_prices();
} else {
_refresh_prices();
}
_refresh_prices();
scroll_to_pending_target();
_refresh_all_visuals();
} else {
@@ -1164,8 +1151,17 @@ namespace mbts.spark {
_refresh_bid_mkt_break();
if (_mkt_break_lines.ask_dirty)
_refresh_ask_mkt_break();
if (_mkt_break_lines.mid_dirty)
if (_mkt_break_lines.mid_dirty) {
switch (_settings.net_change) {
case LadderSettings.NetChangeTerms.BasisPoints:
case LadderSettings.NetChangeTerms.NetBasisPoints:
_refresh_prices();
break;
default:
break;
}
_refresh_mid_mkt_break();
}
_refresh_fair_value_lines(false);
if (_mkt_break_lines.session_low_dirty)
_refresh_session_low_line();
@@ -1175,8 +1171,16 @@ namespace mbts.spark {
_refresh_session_range();
if (last_trade_changed) {
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
if (last_index > 0)
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
if (last_index > 0) {
LadderRender.render_last(
_last_vis_tup.visual,
_last_vis_tup.glyph_context,
_last_trade,
last_index,
_settings.row_height,
_settings.last_column,
_fmt_mkt_depth_item);
}
}
if (depth_changed)
_refresh_sizes();
@@ -1191,25 +1195,27 @@ namespace mbts.spark {
}
}
void _refresh_prices() {
void _refresh_prices(Column column = null) {
if (_settings is null) {
return;
}
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
LadderRender.render_prices(
_price_vis_tup.visual, _price_vis_tup.glyph_context,
_price_list, _vm.md.settlement_price,
_settings.net_change == LadderSettings.NetChangeTerms.Ticks,
_settings.row_height, _settings.price_column,
_spec);
} else {
LadderRender.render_prices(
_price_vis_tup.visual, _price_vis_tup.glyph_context,
_price_list, _settings.row_height,
_settings.price_column, _spec,
_settings.price_display_format);
}
column ??= _price_column;
var ref_price = _settings.net_change switch {
LadderSettings.NetChangeTerms.BasisPoints or
LadderSettings.NetChangeTerms.NetBasisPoints or
LadderSettings.NetChangeTerms.Ticks => _get_mid_market_break(),
_ => 0.0
};
LadderRender.render_prices(
column.visual_tup.visual,
column.visual_tup.glyph_context,
_price_list,
ref_price,
_settings.net_change,
_settings.row_height,
column.settings,
_spec,
_settings.price_display_format);
}
void _refresh_all_visuals() {
@@ -1265,33 +1271,50 @@ namespace mbts.spark {
}
void _refresh_session_low_line() {
if (_settings.session_low_line.show && _mkt_break_lines.low_render_index.HasValue)
_render_session_line(_session_low_vis, _mkt_break_lines.low_render_index.Value, _settings.session_low_line.color);
if (_settings.session_low_line.show) {
_render_session_line(_session_low_vis, _mkt_break_lines.low_render_index ?? -1, _settings.session_low_line.color);
}
}
void _refresh_session_high_line() {
if (_settings.session_high_line.show && _mkt_break_lines.high_render_index.HasValue)
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index.Value, _settings.session_high_line.color);
if (_settings.session_high_line.show) {
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index ?? -1, _settings.session_high_line.color);
}
}
void _refresh_last_trade() {
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
if (last_index > 0)
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
LadderRender.render_last(
_last_vis_tup.visual,
_last_vis_tup.glyph_context,
_last_trade,
last_index,
_settings.row_height,
_settings.last_column,
_fmt_mkt_depth_item);
}
void _refresh_piq() {
if (!_settings.piq_column.show)
return;
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, _price_list, _vm.price_vs_piq, _settings.row_height, _spec, _settings.piq_column, _fmt_mkt_quantity);
LadderRender.render_piqs(
_piq_vis_tup.visual,
_last_vis_tup.glyph_context,
_price_list,
_vm.price_vs_piq,
_settings.row_height,
_spec,
_settings.piq_column,
_fmt_mkt_quantity);
}
void _refresh_sizes() {
Log.assert_d(_price_list != null);
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, _spec, _settings.bid_column, _fmt_mkt_depth_item, _lighten_market_cells, _bid_market_cell_background);
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, _spec, _settings.ask_column, _fmt_mkt_depth_item, _lighten_market_cells, _ask_market_cell_background);
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _depth.Bids, _settings.row_height, _spec, _settings.bid_column, _fmt_price_level);
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _depth.Asks, _settings.row_height, _spec, _settings.ask_column, _fmt_price_level);
}
/// <summary>
@@ -1346,7 +1369,7 @@ namespace mbts.spark {
}
// regardless of if we've arrived here via a new Last (and new direction arrow)
// or we're reusing the old one, this format bit is the same
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size)} ";
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size, _last_trade.price)} ";
}
}
@@ -1362,7 +1385,7 @@ namespace mbts.spark {
return;
if (_price_list is null) {
_price_list = new LadderPriceList {
security = security
regional_tick = _spec
};
}
_price_list.reset(null, null, _initial_row_count);
@@ -1399,7 +1422,10 @@ namespace mbts.spark {
double _get_bid_market_break() {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
return LadderPriceLineViewAdapter.bid_line(best_bid, best_ask, _vm.settlement, _spec);
var v = best_bid
.if_nan(_spec.tickdown(best_ask))
.if_nan(_vm.settlement);
return _spec.round_to_tick(v, RoundingMode.AbsDown);
}
double _normalize_price(double price) =>
@@ -1408,13 +1434,20 @@ namespace mbts.spark {
double _get_ask_market_break() {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
return LadderPriceLineViewAdapter.ask_line(best_bid, best_ask, _vm.settlement, _spec);
var v = _spec.tickdown(best_ask)
.if_nan(best_bid)
.if_nan(_vm.settlement);
return _spec.round_to_tick(v, RoundingMode.AbsDown);
}
double _get_mid_market_break() {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
return LadderPriceLineViewAdapter.mid_line(best_bid, best_ask, _vm.settlement, _spec);
var v = TickUtils.mid_tick(_spec, best_bid, best_ask)
.if_nan(best_bid)
.if_nan(_spec?.tickdown(best_ask) ?? double.NaN)
.if_nan(_vm.settlement);
return _spec.round_to_tick(v, RoundingMode.AbsDown);
}
double _get_session_low() => _normalize_price(_get_session_low_raw());
@@ -1446,7 +1479,13 @@ namespace mbts.spark {
_pending_scroll_to_origin |= _settings == null;
_invalidate_spec();
if (_price_list != null) {
_price_list.security = security;
_pending_price_list = null;
if (_price_list.Count > 0) {
var high_price = _price_list.First();
var new_tickdiff = _spec.count_ticks(high_price);
_price_list.high_tick = (long)new_tickdiff;
}
_price_list.regional_tick = _spec;
}
_bid_vis_tup.glyph_context = glyph_context(s.bid_column);
_ask_vis_tup.glyph_context = glyph_context(s.ask_column);
@@ -1458,9 +1497,13 @@ namespace mbts.spark {
static GlyphContext glyph_context(LadderColumnSettings cs) =>
GlyphContext.create(cs.font_family, cs.font_weight);
var price_display = security.regional_ticks().tick_factors(0).to_api_tick_display_settingsi();
_fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(
_settings.size_display, _qty_display);
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(_qty_display);
_settings.size_display, _qty_display, price_display);
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(
_settings.size_display, _qty_display, price_display);
_fmt_price_level_impl = MDDisplayHelper.get_formatter_lpl(
_settings.size_display, _qty_display, price_display);
_set_columns();
_apply_column_visual_offset();
if (prev_row_height != _settings.row_height)
@@ -1470,18 +1513,16 @@ namespace mbts.spark {
_mkt_break_lines.fair_value_line_views.Clear();
// Clear and re-add the columns to the viewer to ensure offset/visibility is correct
_visual_host.Clear();
if (LadderSettings.mimic_tt) {
// The session_range_vis sits beneath the grid backgrounds (on the
// z-axis) to avoid obfuscating the font color (dj wants it to be
// pure white). Historically, it sat atop the price column, with a
// low opacity to slightly darken the range of prices inside of the
// session range (i.e., the price-band of the day's trades). In
// conjuction with this change, the price column's background
// opacity is made slightly less than 1 (in the default settings),
// while the font color remains fully opaque, so this layer will
// darken the background without affecting the font color.
_visual_host.AddVisualChild(_session_range_vis);
}
// The session_range_vis sits beneath the grid backgrounds (on the
// z-axis) to avoid obfuscating the font color (dj wants it to be
// pure white). Historically, it sat atop the price column, with a
// low opacity to slightly darken the range of prices inside of the
// session range (i.e., the price-band of the day's trades). In
// conjuction with this change, the price column's background
// opacity is made slightly less than 1 (in the default settings),
// while the font color remains fully opaque, so this layer will
// darken the background without affecting the font color.
_visual_host.AddVisualChild(_session_range_vis);
// Renders the grid lines and column backgrounds
_visual_host.AddVisualChild(_gridvis);
// Orders column
@@ -1521,8 +1562,6 @@ namespace mbts.spark {
with_vis => _visual_host.RemoveVisualChild(with_vis.vis));
_visual_host.AddVisualChild(_session_low_vis);
_visual_host.AddVisualChild(_session_high_vis);
if (!LadderSettings.mimic_tt)
_visual_host.AddVisualChild(_session_range_vis);
_render_grid_background();
_init_tactile_adorner();
// this effectively sets the background of the "Last" column //deleted to fix color
@@ -1531,6 +1570,7 @@ namespace mbts.spark {
// }
scroll_to_pending_target();
visible_columns_changed?.Invoke();
_refresh_all_visuals();
}
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
@@ -1538,16 +1578,27 @@ namespace mbts.spark {
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
var impl = _fmt_mkt_depth_item_impl;
impl ??= MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default(), _qty_display);
impl ??= MDDisplayHelper.get_formatter_nps(
MarketSizeDisplay.get_default(), _qty_display, _spec.tick_factors(0).to_api_tick_display_settingsi());
return impl(value);
}
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
string _fmt_price_level(LadderPriceLevel value) {
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
var impl = _fmt_price_level_impl;
impl ??= MDDisplayHelper.get_formatter_lpl(
MarketSizeDisplay.get_default(), _qty_display, security.regional_ticks().tick_factors(0).to_api_tick_display_settingsi());
return impl(value);
}
// This is designed to be a redundant null check for _fmt_mkt_quantity_impl.
string _fmt_mkt_quantity(double value) {
string _fmt_mkt_quantity(double value, double price) {
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
var impl = _fmt_mkt_quantity_impl;
return impl is null ? value.ToString() : impl(value);
return impl is null ? value.ToString() : impl(value, price);
}
void _init_tactile_adorner() {
@@ -0,0 +1,98 @@
namespace mbts.spark;
using bts;
using bts.utils;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
readonly struct LadderPriceLevel : IEquatable<LadderPriceLevel> {
public LadderPriceLevel(double price = double.NaN, double volume = 0, double notionalValue = double.NaN) {
Price = price;
Volume = volume;
NotionalValue = notionalValue.is_nan() ? volume * price : notionalValue;
}
public static LadderPriceLevel Empty => new();
public double Price { get; }
public double Volume { get; }
public double NotionalValue { get; }
public LadderPriceLevel Add(double price, double volume) {
if (!(volume > 0)) {
return this;
}
if (!(Volume > 0)) {
return new(price, volume);
}
Log.assert(Price.is_real());
Log.assert(price.is_real());
var newVolume = Volume + volume;
var newPrice = (Price * Volume + price * volume) / newVolume;
return new(newPrice, newVolume);
}
public LadderPriceLevel Add(IEnumerable<(double Price, double Volume)> items) {
var (totalPrice, totalVolume) = items.Sum();
return totalVolume > 0
? Add(totalPrice / totalVolume, totalVolume)
: this;
}
public Builder ToBuilder() =>
new() { Cost = Price, Volume = Volume };
public LadderPriceLevel With(double price = double.NaN, double volume = double.NaN, double notionalValue = double.NaN) =>
new(price.if_nan(Price), volume.if_nan(Volume), notionalValue.if_nan(NotionalValue));
public bool Equals(LadderPriceLevel other) =>
MathUtils.epsilon_eq_comparer.Equals(Price, other.Price)
&& MathUtils.epsilon_eq_comparer.Equals(Volume, other.Volume);
public override bool Equals([NotNullWhen(true)] object obj) =>
obj is LadderPriceLevel other && Equals(other);
public override int GetHashCode() =>
HashCode.Combine(
MathUtils.epsilon_eq_comparer.GetHashCode(Price),
MathUtils.epsilon_eq_comparer.GetHashCode(Volume));
public class Builder {
double notionalValue = double.NaN;
public double Cost { get; set; } = double.NaN;
public double Volume { get; set; } = 0;
public double NotionalValue => notionalValue.is_nan() ? Volume * Cost : notionalValue;
public void Add(double price, double volume) {
if (!(volume > 0)) {
return;
}
Log.assert(price.is_real());
Cost += price * volume;
Volume += volume;
}
public void Add(double price, double volume, double notionalValue) {
if (!(volume > 0)) {
return;
}
Log.assert(price.is_real());
Cost += price * volume;
Volume += volume;
if (this.notionalValue.is_nan()) {
this.notionalValue = notionalValue;
} else{
this.notionalValue += notionalValue;
}
}
public LadderPriceLevel ToImmutable() =>
Volume > 0
? new(Cost / Volume, Volume, NotionalValue)
: Empty;
}
}
@@ -1,62 +0,0 @@
using bts.api;
namespace mbts.spark {
using RegionalTickFactors = user.settings.RegionalTickFactors;
class LadderPriceLineViewAdapter {
public static double bid_line(
double bid, double ask, double settlement, RegionalTickFactors spec) {
return TickUtils.normalize_price(spec, impl());
double impl() {
if (!double.IsNaN(bid)) {
return bid;
}
if (!double.IsNaN(ask)) {
return _try_tick_down(spec, ask);
}
return settlement;
}
}
public static double ask_line(
double bid, double ask, double settlement, RegionalTickFactors spec) {
return TickUtils.normalize_price(spec, impl());
double impl() {
if (!double.IsNaN(ask)) {
return _try_tick_down(spec, ask);
}
if (!double.IsNaN(bid)) {
return bid;
}
return settlement;
}
}
public static double mid_line(
double bid, double ask, double settlement, RegionalTickFactors spec) {
return TickUtils.normalize_price(spec, impl());
double impl() {
if (double.IsNaN(bid)) {
if (double.IsNaN(ask)) {
return settlement;
}
return _try_tick_down(spec, ask);
}
if (double.IsNaN(ask)) {
return bid;
}
if (spec is not null) {
return TickUtils.mid_tick(spec, bid, ask);
}
return bid;
}
}
static double _try_tick_down(RegionalTickFactors spec, double value) {
if (spec is not null) {
return spec.tickdown(value);
}
return value;
}
}
}
@@ -16,33 +16,34 @@ namespace mbts.spark {
long _length;
long _high_tick;
SecurityImmutable? _security;
RegionalTickFactors _regional_tick_backing;
RegionalTickFactors _regional_tick =>
_regional_tick_backing ??= TickUtils.get_regional_ticks(_security);
public LadderPriceList() { }
public LadderPriceList()
: this(RegionalTickFactors.Default) { }
public LadderPriceList(RegionalTickFactors regional_tick) =>
this.regional_tick = regional_tick;
public LadderPriceList(LadderPriceList other) {
_length = other._length;
_high_tick = other._high_tick;
_security = other._security;
regional_tick = other.regional_tick;
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public SecurityImmutable? security {
get => _security;
RegionalTickFactors _regional_tick;
public RegionalTickFactors regional_tick {
get => _regional_tick;
set {
if (value == _security) {
return;
if (value is null) {
_regional_tick = RegionalTickFactors.Default;
} else {
_regional_tick = value;
}
_security = value;
_regional_tick_backing = null;
}
}
public ContractSpecificationImmutable spec { get; set; }
public long high_tick {
get => _high_tick;
set => _high_tick = value;
@@ -54,8 +55,8 @@ namespace mbts.spark {
}
public double start {
get => _regional_tick.tickup<double>(0, (uint)high_tick);
set => high_tick = _regional_tick.tickdiff(value, 0) ?? high_tick;
get => regional_tick.tickup<double>(0, (uint)high_tick);
set => high_tick = regional_tick.tickdiff(value, 0) ?? high_tick;
}
public long length {
@@ -80,7 +81,7 @@ namespace mbts.spark {
public bool IsReadOnly => false;
public double this[int idx] {
get => _regional_tick.tick<double>(0, high_tick - idx);
get => regional_tick.tick<double>(0, high_tick - idx);
set => throw new NotSupportedException();
}
@@ -94,7 +95,7 @@ namespace mbts.spark {
GetEnumerator();
public int IndexOf(double item) {
var idx = _regional_tick.tickdiff(start, item) ?? -1;
var idx = regional_tick.tickdiff(start, item) ?? -1;
if (idx < 0 || idx >= length) {
return -1;
}
@@ -209,7 +210,7 @@ namespace mbts.spark {
other != null &&
_high_tick == other._high_tick &&
_length == other._length &&
_regional_tick.safe_equals(other._regional_tick);
regional_tick.safe_equals(other.regional_tick);
public override bool Equals(object obj) =>
Equals(obj as LadderPriceList);
@@ -218,7 +219,7 @@ namespace mbts.spark {
HashCode.Combine(_high_tick, _length);
double _tick_to_price(long tick) =>
_regional_tick.tick<double>(0, tick);
regional_tick.tick<double>(0, tick);
void _add_tick(long tick) {
int idx;
@@ -31,19 +31,19 @@ namespace mbts.spark {
Side side,
double quantity,
double filled,
double price,
LadderSettings.BadgeStyle style,
Func<double, string> fmt_value = null
Func<double, double, string> fmt_value = null
) {
if (fmt_value is null)
fmt_value = FormattingDelegates.default_string<double>();
fmt_value ??= (v, p) => v.ToString();
switch (style) {
case LadderSettings.BadgeStyle.Alternate:
var working_qty = quantity > filled ? quantity - filled : 0;
return new[] {
$"{side.abbreviate()}:{fmt_value(filled)}",
$"W:{fmt_value(working_qty)}"
$"{side.abbreviate()}:{fmt_value(filled, price)}",
$"W:{fmt_value(working_qty, price)}"
};
default: return new[] { $"{fmt_value(filled)}/{fmt_value(quantity)}" };
default: return new[] { $"{fmt_value(filled, price)}/{fmt_value(quantity, price)}" };
}
}
@@ -57,8 +57,7 @@ namespace mbts.spark {
static class LadderRender {
// Note: the orders-column has a separate "row_padding" value: Badge.DefaultMargin
// (see how it is incorporated in measure_row_height() below)
public static Thickness row_padding => LadderSettings.mimic_tt ?
new Thickness(4, 2, 4, 2) : new Thickness(6);
public static Thickness row_padding => new Thickness(4, 2, 4, 2);
const string tick_separator_period = ".";
const string tick_separator_dash = "-";
@@ -102,17 +101,64 @@ namespace mbts.spark {
settings.font_size, settings.width, row_height, HorizontalAlignment.Right,
-4, VerticalAlignment.Center, row * row_height, true);
public static GlyphRun[] format_prices(IList<double> prices, double reference_price, bool format_in_tick_terms, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, RegionalTickFactors spec) {
public static GlyphRun[] format_prices(
IList<double> prices,
double reference_price,
LadderSettings.NetChangeTerms terms,
double row_height,
LadderColumnSettings settings,
GlyphContext glyph_context,
RegionalTickFactors spec,
LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default
) {
var result = new GlyphRun[prices.Count];
string basis_pts_fmt = terms switch {
LadderSettings.NetChangeTerms.BasisPoints or
LadderSettings.NetChangeTerms.NetBasisPoints => get_basis_pts_fmt(),
_ => null
};
Parallel.For(0, prices.Count, i =>
result[i] = format_text(spec.format(prices[i] - reference_price, format_in_tick_terms), i, row_height, settings, glyph_context));
return result;
}
public static GlyphRun[] format_prices(IList<double> prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, RegionalTickFactors spec, LadderSettings.PriceDisplayFormat price_display_format) {
var result = new GlyphRun[prices.Count];
Parallel.For(0, prices.Count, i =>
result[i] = format_text(spec.format(prices[i]), i, row_height, settings, glyph_context, price_display_format));
result[i] = format_text(fmt(i), i, row_height, settings, glyph_context, price_display_format));
return result;
string fmt(int i) {
switch (terms) {
case LadderSettings.NetChangeTerms.Dollars:
return prices[i].ToString("0.00");
case LadderSettings.NetChangeTerms.BasisPoints: {
if (!reference_price.is_real_non_zero()) {
return spec.format(double.NaN);
}
var basis_pts = prices[i] * 10000.0 / reference_price;
return basis_pts.ToString(basis_pts_fmt);
}
case LadderSettings.NetChangeTerms.NetBasisPoints: {
if (!reference_price.is_real_non_zero()) {
return spec.format(double.NaN);
}
var net_basis_pts = prices[i] * 10000.0 / reference_price - 10000.0;
return net_basis_pts.ToString(basis_pts_fmt);
}
default:
return spec.format(prices[i]);
}
}
string get_basis_pts_fmt() {
if (!reference_price.is_real_non_zero()) {
return "0.000";
}
var tick_sz = spec.ticksize(reference_price);
var basis_pt = reference_price.abs() / 10000.0;
var precision_log10 = Math.Log10(tick_sz / basis_pt);
int n = 0;
if (precision_log10 < 0) {
n = (int)Math.Ceiling(-precision_log10);
}
return n < 1
? "0"
: "0." + new string('0', n);
}
}
public static void clear_prices(DrawingVisual price, int row_count, double row_height, LadderColumnSettings settings, GlyphContext glyph_context) {
@@ -128,51 +174,20 @@ namespace mbts.spark {
public static void render_prices(
DrawingVisual price,
GlyphContext glyph_context,
IList<double> price_list,
IList<double> dprices,
double wrt_price,
bool format_in_tick_terms,
LadderSettings.NetChangeTerms terms,
double row_height,
LadderColumnSettings settings,
RegionalTickFactors tick) {
if (price_list.IsNullOrEmpty()) {
RegionalTickFactors tick,
LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default) {
if (dprices.IsNullOrEmpty()) {
return;
}
render_prices(
price, glyph_context, price_list, row_height, settings, tick,
wrt_price, format_in_tick_terms);
}
public static void render_prices(
DrawingVisual price,
GlyphContext glyph_context,
IList<double> dprices,
double row_height,
LadderColumnSettings settings,
RegionalTickFactors tick,
double wrt_price,
bool format_in_tick_terms) {
var foreground = settings.foreground.rgb_brush();
//var background = new SolidColorBrush(settings.background.rgb_color());
var fprices = format_prices(dprices, wrt_price, format_in_tick_terms, row_height, settings, glyph_context, tick);
using (var dc = price.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, dprices.Count)) {
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
for (int x = 0; x < dprices.Count; ++x)
dc.DrawGlyphRun(foreground, fprices[x]);
}
}
public static void render_prices(
DrawingVisual price,
GlyphContext glyph_context,
IList<double> dprices,
double row_height,
LadderColumnSettings settings,
RegionalTickFactors tick,
LadderSettings.PriceDisplayFormat price_display_format) {
var foreground = settings.foreground.rgb_brush();
//var background = new SolidColorBrush(settings.background.rgb_color());
var fprices = format_prices(dprices, row_height, settings, glyph_context, tick, price_display_format);
var fprices = format_prices(
dprices, wrt_price, terms, row_height, settings, glyph_context, tick, price_display_format);
using (var dc = price.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, dprices.Count)) {
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
@@ -198,69 +213,78 @@ namespace mbts.spark {
}
}
public static void RenderSizes(
DrawingVisual dv,
GlyphContext glyphContext,
int originIndex,
IList<double> prices,
IEnumerable<LadderPriceLevel> levels,
double rowHeight,
RegionalTickFactors spec,
LadderColumnSettings settings,
Func<LadderPriceLevel, string> fmt) {
var bg = settings.background.rgb_color().blend((BTSColor)0x2AFFFFFF).to_brush();
using (var dc = dv.RenderOpen())
using (_draw_with_clip(dc, settings, rowHeight, prices.Count))
using (var levelsEn = (levels ?? Enumerable.Empty<LadderPriceLevel>()).GetEnumerator()) {
var fg = settings.foreground.rgb_brush();
var currIdx = originIndex;
var currPrice = prices[currIdx];
while (levelsEn.MoveNext()) {
var level = levelsEn.Current;
var ticks = spec.count_ticks(level.Price);
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : level.Price;
// prices are in descending order, so get diff from curr_price - price
var tickDiff = spec.tickdiff(currPrice, price).assert_value();
if (!MathUtils.try_increment(ref currIdx, tickDiff) || currIdx < 0 || currIdx >= prices.Count) {
continue;
}
currPrice = price;
if (bg is not null) {
var y = currIdx * rowHeight + GRID_LINE_HEIGHT;
var height = rowHeight - GRID_LINE_HEIGHT;
dc.DrawRectangle(bg, null, new Rect(0, y, settings.width, height));
}
var gr = format_text(fmt(level), currIdx, rowHeight, settings, glyphContext);
dc.DrawGlyphRun(fg, gr);
}
}
}
public static IEnumerable<LadderPriceLevel> GetLevels(IEnumerable<(double Price, double Volume, double NotionalValue)> depth, RegionalTickFactors spec) =>
depth.TakeWhile(nps => nps.Volume > 0 && nps.Price.is_real())
.AggregateConsecutiveGroups(
LadderPriceLevel.Empty.ToBuilder,
(bd, rung) => {
bd.Add(rung.Price, rung.Volume, rung.NotionalValue);
return bd;
},
rung => {
var ticks = spec.count_ticks(rung.Price);
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.Price;
})
.Select(bd => bd.ToImmutable());
public static void render_sizes(
DrawingVisual dv,
GlyphContext glyph_context,
int origin_index,
IList<double> prices,
NativePriceSize[] rungs,
IEnumerable<(double Price, double Volume, double NotionalValue)> rungs,
double row_height,
RegionalTickFactors spec,
LadderColumnSettings settings,
Func<NativePriceSize, string> fmt,
bool lighten_market_cells,
Color? market_cell_background) {
//var background = new SolidColorBrush(settings.background.rgb_color());
Brush market_cell_background_brush;
if (market_cell_background.HasValue) {
market_cell_background_brush = new SolidColorBrush(market_cell_background.Value);
} else if (lighten_market_cells) {
market_cell_background_brush = settings.background.rgb_color()
.blend((BTSColor)0x2AFFFFFF)
.to_brush();
} else {
market_cell_background_brush = null;
}
// market_cell_background_brush = settings.background.rgb_color().blend((BTSColor)0xFF000000).to_brush();
using (var dc = dv.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, prices.Count * row_height));
if (!prices.within_range(origin_index) || rungs.is_null_or_empty())
return;
var foreground = settings.foreground.rgb_brush();
int curr_index = origin_index;
double curr_price = prices[curr_index];
NativePriceSize curr_size = NativePriceSize.NO_MARKET;
var row_tick_groups = rungs
.TakeWhile(nps => !nps.is_nan)
.AggregateConsecutiveGroups(() => NativePriceSize.NO_MARKET,
(aggr, rung) => rung.with(
direct_size: aggr.direct_size + rung.direct_size,
implied_size: aggr.implied_size + rung.implied_size),
rung => {
var ticks = spec.count_ticks(rung.price);
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
});
foreach (var rung in row_tick_groups) {
//double price = spec.round_to_tick(rung.price);
var ticks = spec.count_ticks(rung.price);
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
// prices are in descending order, so get diff from curr_price - price
var tick_diff = spec.tickdiff(curr_price, price).assert_value();
if (!MathUtils.try_increment(ref curr_index, tick_diff)) {
continue;
}
curr_price = price;
if (curr_index >= 0 && curr_index < prices.Count) {
if (market_cell_background_brush != null)
dc.DrawRectangle(market_cell_background_brush, null,
new Rect(0, curr_index * row_height + GRID_LINE_HEIGHT,
settings.width, row_height - GRID_LINE_HEIGHT));
var gr = format_text(fmt(rung), curr_index, row_height, settings, glyph_context);
dc.DrawGlyphRun(foreground, gr);
} else break;
}
}
Func<LadderPriceLevel, string> fmt) {
RenderSizes(
dv,
glyph_context,
origin_index,
prices,
GetLevels(rungs, spec),
row_height,
spec,
settings,
fmt);
}
public static void render_volumes(
@@ -273,7 +297,7 @@ namespace mbts.spark {
VolumeColumnSettings settings,
int last_index,
string last_size_and_direction,
Func<double, string> fmt_value) {
Func<double, double, string> fmt_value) {
//
if (!data.Any()) {
_clear_visual(dv);
@@ -296,12 +320,12 @@ namespace mbts.spark {
_clear_visual(dv);
return;
}
fmt_value ??= FormattingDelegates.default_string<double>();
fmt_value ??= (v, p) => v.ToString();
// inflate the max so that the graph never fills the available space
double max = data.Max(v => v.Volume) * 1.1;
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
/// local methods
void render_cell(DrawingContext dc, int r, VolumeAtPriceLevelImmutable vap) {
void render_cell(DrawingContext dc, int r, double price, VolumeAtPriceLevelImmutable vap) {
Brush text_brush = settings.general.foreground.rgb_brush();
double y = row_height * r;
var last_trade_here = r == last_index;
@@ -328,11 +352,11 @@ namespace mbts.spark {
}
x_bar += width;
if (show_numbers)
numbers_text.Add(fmt_value(v));
numbers_text.Add(fmt_value(v, vap.Price));
}
if (settings.total.show_numbers) {
numbers_text.Add(fmt_value(vap.Volume));
numbers_text.Add(fmt_value(vap.Volume, vap.Price));
}
if (settings.show_ltq && last_trade_here) {
numbers_text.Clear();
@@ -359,9 +383,17 @@ namespace mbts.spark {
}
}
public delegate void RenderCell<T>(DrawingContext dc, int row, T data_item);
public delegate void RenderCell<T>(DrawingContext dc, int row, double price, T data_item);
static void _render_cells<T>(DrawingVisual dv, double row_height, IEnumerable<(double price, T value)> cells, LadderColumnSettings settings, IList<double> prices, RegionalTickFactors spec, RenderCell<T> render_cell) {
static void _render_cells<T>(
DrawingVisual dv,
double row_height,
IEnumerable<(double price, T value)> cells,
LadderColumnSettings settings,
IList<double> prices,
RegionalTickFactors spec,
RenderCell<T> render_cell
) {
using (var dc = dv.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
if (prices.is_null_or_empty() || cells.is_null_or_empty()) {
@@ -381,7 +413,7 @@ namespace mbts.spark {
var check_price = spec.tickdown(max_price, idx);
Log.assert(spec.tick_equals(price, check_price));
Log.assert(idx >= 0 && idx < prices.Count);
render_cell(dc, idx, value);
render_cell(dc, idx, price, value);
}
}
}
@@ -393,10 +425,10 @@ namespace mbts.spark {
int index,
double row_height,
LadderColumnSettings settings,
Func<double, string> fmt_value) {
fmt_value ??= FormattingDelegates.default_string<double>();
Func<NativePriceSize, string> fmt_value) {
fmt_value ??= nps => nps.size.ToString();
using (var dc = dv.RenderOpen()) {
var gr = format_text(fmt_value(last.size), index, row_height, settings, glyph_context);
var gr = format_text(fmt_value(last), index, row_height, settings, glyph_context);
dc.DrawGlyphRun(settings.foreground.rgb_brush(), gr);
}
}
@@ -410,13 +442,13 @@ namespace mbts.spark {
double row_height,
RegionalTickFactors spec,
LadderColumnSettings settings,
Func<double, string> fmt_value
Func<double, double, string> fmt_value
) {
fmt_value ??= FormattingDelegates.default_string<double>();
fmt_value ??= (v, p) => v.ToString();
var fg = settings.foreground.rgb_brush();
_render_cells(dv, row_height, pvp, settings, prices, spec, (dc, row, piq) =>
_render_cells(dv, row_height, pvp, settings, prices, spec, (dc, row, price, piq) =>
dc.DrawGlyphRun(
fg, format_text(fmt_value(piq), row, row_height, settings, glyph_context)));
fg, format_text(fmt_value(piq, price), row, row_height, settings, glyph_context)));
}
public static void render_mkt_break(DrawingVisual dv, int index, double row_height, uint color) =>
File diff suppressed because it is too large. Load diff
@@ -39,6 +39,9 @@
<cv:EnumCharacterCasingConverter x:Key="casing_converter"/>
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel" />
<cv:ResponsiveTextConverter x:Key="BidPriceTextConverter" LongText="Bid:" ShortText="B:" Threshold="99" />
<cv:ResponsiveTextConverter x:Key="AskPriceTextConverter" LongText="Ask:" ShortText="A:" Threshold="99" />
<cv:ResponsiveTextConverter x:Key="MidPriceTextConverter" LongText="Mid:" ShortText="M:" Threshold="99" />
<cv:ResponsiveTextConverter x:Key="DayTextConverter" LongText="Day:" ShortText="D:" Threshold="99" />
<cv:ResponsiveTextConverter x:Key="HighTextConverter" LongText="High:" ShortText="H:" Threshold="99" />
<cv:ResponsiveTextConverter x:Key="LowTextConverter" LongText="Low:" ShortText="L:" Threshold="99" />
@@ -179,7 +182,7 @@
</Style>
<Style x:Key="CancelAll" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Grid.Row" Value="2" />
<Setter Property="Grid.Row" Value="0"/>
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center">Cancel All</TextBlock>
@@ -217,10 +220,7 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TT_CancelAll" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Setter Property="Grid.Row" Value="0"/>
</Style>
<Style x:Key="TT_CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Content">
<Setter.Value>
@@ -228,7 +228,7 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TT_CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
<Setter Property="Grid.Row" Value="2" />
<Setter Property="Content">
<Setter.Value>
@@ -236,83 +236,6 @@
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Grid.Row" Value="0" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{Binding bid_foreground}">
Cancel Buys
</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{Binding bid_background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99167ed3" />
<Setter
TargetName="Border"
Property="BorderBrush"
Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}" />
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Foreground="{Binding ask_foreground}">
Cancel Sells
</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{Binding ask_background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True" >
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"
Name="button_content" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
<Setter
TargetName="Border"
Property="BorderBrush"
Value="{u:BTSColor UI.Button.mouseover_border}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SettingsButton" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
<Setter Property="Content">
<Setter.Value>
@@ -363,13 +286,103 @@
<Setter Property="Grid.Column" Value="2"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="0,0,0,2"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Padding" Value="4,1"/>
</Style>
<Style x:Key="LeftPanelPricePointText" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}"/>
</Style>
<Style x:Key="LeftPanelPricePointButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Grid.Column" Value="2" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="Margin" Value="0,0,0,2" />
<Setter Property="Padding" Value="4,1" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="0"
Height="Auto"
Padding="0"
SnapsToDevicePixels="True">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal" />
<VisualState Name="MouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="HoverRect" Storyboard.TargetProperty="Fill.Opacity" BeginTime="0" Duration="00:00:00.15" To="0.20">
<DoubleAnimation.EasingFunction>
<QuadraticEase />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.mouseover_border}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.pressed_background}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.pressed_border}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.disabled_background}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.disabled_border}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="FocusStates">
<VisualState Name="Unfocused" />
<VisualState Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.focused_border}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Rectangle Name="HoverRect" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" IsHitTestVisible="False">
<Rectangle.Fill>
<SolidColorBrush Color="White" Opacity="0" />
</Rectangle.Fill>
</Rectangle>
<ContentPresenter
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<sys:Double x:Key="MaxZoomFactor">1.5</sys:Double>
<sys:Double x:Key="MinZoomFactor">0.5</sys:Double>
</ResourceDictionary>
@@ -434,6 +447,9 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -441,9 +457,123 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Day Change (label) -->
<!-- Ask Price (label) -->
<TextBlock
Grid.Row="0"
Text="{Binding left_panel_width, Converter={StaticResource AskPriceTextConverter}}"
Visibility="{Binding
ask_price_style.actual_show_label,
Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Ask Price (value) -->
<Button
Grid.Row="0"
Visibility="{Binding
ask_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Ask"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding ask_price}"
Style="{StaticResource LeftPanelPricePointButton}">
<Button.Background>
<SolidColorBrush Color="{Binding ask_price_style.background_color}" Opacity="1" />
</Button.Background>
<TextBlock
FontFamily="{Binding ask_price_style.font_family}"
FontSize="{Binding ask_price_style.font_size}"
FontWeight="{Binding ask_price_style.font_weight}"
Foreground="{Binding
ask_price_style.font_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="ask_price"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Button>
<!-- Mid Price (label) -->
<TextBlock
Grid.Row="1"
Text="{Binding left_panel_width, Converter={StaticResource MidPriceTextConverter}}"
Visibility="{Binding
mid_price_style.actual_show_label,
Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Mid Price (value) -->
<Button
Grid.Row="1"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding mid_price}"
Visibility="{Binding
mid_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Mid"
Style="{StaticResource LeftPanelPricePointButton}">
<Button.Background>
<SolidColorBrush Color="{Binding mid_price_style.background_color}" Opacity="1" />
</Button.Background>
<TextBlock
FontFamily="{Binding mid_price_style.font_family}"
FontSize="{Binding mid_price_style.font_size}"
FontWeight="{Binding mid_price_style.font_weight}"
Foreground="{Binding
mid_price_style.font_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="mid_price"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Button>
<!-- Bid Price (label) -->
<TextBlock
Grid.Row="2"
Text="{Binding left_panel_width, Converter={StaticResource BidPriceTextConverter}}"
Visibility="{Binding
bid_price_style.actual_show_label,
Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Bid Price (value) -->
<Button
Grid.Row="2"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding bid_price}"
Visibility="{Binding
bid_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Bid"
Style="{StaticResource LeftPanelPricePointButton}">
<Button.Background>
<SolidColorBrush Color="{Binding bid_price_style.background_color}" Opacity="1" />
</Button.Background>
<TextBlock
FontFamily="{Binding bid_price_style.font_family}"
FontSize="{Binding bid_price_style.font_size}"
FontWeight="{Binding bid_price_style.font_weight}"
Foreground="{Binding
bid_price_style.font_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="bid_price"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Button>
<!-- Day Change (label) -->
<TextBlock
Grid.Row="3"
Text="{Binding left_panel_width, Converter={StaticResource DayTextConverter}}"
Visibility="{Binding
day_change_style.actual_show_label,
@@ -452,7 +582,7 @@
<!-- Day Change (value) -->
<Border
Grid.Row="0"
Grid.Row="3"
Background="{Binding
day_change_background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
@@ -479,7 +609,7 @@
<!-- High (label) -->
<TextBlock
Grid.Row="1"
Grid.Row="4"
Text="{Binding left_panel_width, Converter={StaticResource HighTextConverter}}"
Visibility="{Binding
session_high_style.actual_show_label,
@@ -487,15 +617,17 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- High (value) -->
<Border
Grid.Row="1"
<Button
Grid.Row="4"
Background="{Binding
session_high_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding session_high}"
Visibility="{Binding
session_high_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Session High"
Style="{StaticResource LeftPanelPricePointBorder}">
Style="{StaticResource LeftPanelPricePointButton}">
<TextBlock
FontFamily="{Binding session_high_style.font_family}"
FontSize="{Binding session_high_style.font_size}"
@@ -504,24 +636,19 @@
session_high_style.font_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<Hyperlink
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding session_high}">
<TextBlock>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="session_high"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Hyperlink>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="session_high"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</Button>
<!-- Low (label) -->
<TextBlock
Grid.Row="2"
Grid.Row="5"
Text="{Binding left_panel_width, Converter={StaticResource LowTextConverter}}"
Visibility="{Binding
session_low_style.actual_show_label,
@@ -529,15 +656,17 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Low (value) -->
<Border
Grid.Row="2"
<Button
Grid.Row="5"
Background="{Binding
session_low_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding session_low}"
Visibility="{Binding
session_low_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Session Low"
Style="{StaticResource LeftPanelPricePointBorder}">
Style="{StaticResource LeftPanelPricePointButton}">
<TextBlock
FontFamily="{Binding session_low_style.font_family}"
FontSize="{Binding session_low_style.font_size}"
@@ -546,24 +675,19 @@
session_low_style.font_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<Hyperlink
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding session_low}">
<TextBlock>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="session_low"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Hyperlink>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="session_low"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</Button>
<!-- Open (label) -->
<TextBlock
Grid.Row="3"
Grid.Row="6"
Text="{Binding left_panel_width, Converter={StaticResource OpenTextConverter}}"
Visibility="{Binding
open_style.actual_show_label,
@@ -571,15 +695,17 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Open (value) -->
<Border
Grid.Row="3"
<Button
Grid.Row="6"
Background="{Binding
open_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding open}"
Visibility="{Binding
open_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Open"
Style="{StaticResource LeftPanelPricePointBorder}">
Style="{StaticResource LeftPanelPricePointButton}">
<TextBlock
FontFamily="{Binding open_style.font_family}"
FontSize="{Binding open_style.font_size}"
@@ -587,23 +713,19 @@
Foreground="{Binding
open_style.font_color, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<Hyperlink Command="{Binding go_to_price_cmd}" CommandParameter="{Binding open}">
<TextBlock>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="open"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Hyperlink>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="open"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</Button>
<!-- Settlement (label) -->
<TextBlock
Grid.Row="4"
Grid.Row="7"
Text="{Binding
left_panel_width, Converter={StaticResource SettlementTextConverter}}"
Visibility="{Binding
@@ -612,15 +734,17 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Settlement (value) -->
<Border
Grid.Row="4"
<Button
Grid.Row="7"
Background="{Binding
settlement_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding settlement}"
Visibility="{Binding
settlement_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Settlement"
Style="{StaticResource LeftPanelPricePointBorder}">
Style="{StaticResource LeftPanelPricePointButton}">
<TextBlock
FontFamily="{Binding settlement_style.font_family}"
FontSize="{Binding settlement_style.font_size}"
@@ -629,23 +753,18 @@
settlement_style.font_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<Hyperlink
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding settlement}">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="settlement"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Hyperlink>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="settlement"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</Button>
<!-- Theo (label) -->
<TextBlock
Grid.Row="5"
Grid.Row="8"
Text="{Binding left_panel_width, Converter={StaticResource TheoTextConverter}}"
Visibility="{Binding
theo_style.actual_show_label,
@@ -653,15 +772,17 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<!-- Theo (value) -->
<Border
Grid.Row="5"
<Button
Grid.Row="8"
Background="{Binding
theo_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Command="{Binding go_to_price_cmd}"
CommandParameter="{Binding theo}"
Visibility="{Binding
theo_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
ToolTip="Theo"
Style="{StaticResource LeftPanelPricePointBorder}">
Style="{StaticResource LeftPanelPricePointButton}">
<TextBlock
FontFamily="{Binding theo_style.font_family}"
FontSize="{Binding theo_style.font_size}"
@@ -669,23 +790,19 @@
Foreground="{Binding
theo_style.font_color, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
Style="{StaticResource LeftPanelPricePointText}">
<Hyperlink Command="{Binding go_to_price_cmd}" CommandParameter="{Binding theo}">
<TextBlock>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="theo"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Hyperlink>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="theo"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Border>
</Button>
<!-- Delta -->
<TextBlock
Grid.Row="6"
Grid.Row="9"
Text="{Binding left_panel_width, Converter={StaticResource DeltaTextConverter}}"
Visibility="{Binding
delta_style.actual_show_label,
@@ -693,7 +810,7 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<Border
Grid.Row="6"
Grid.Row="9"
Background="{Binding
delta_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
@@ -714,7 +831,7 @@
<!-- Vega -->
<TextBlock
Grid.Row="7"
Grid.Row="10"
Text="{Binding left_panel_width, Converter={StaticResource VegaTextConverter}}"
Visibility="{Binding
vega_style.actual_show_label,
@@ -722,7 +839,7 @@
Style="{StaticResource LeftPanelPricePointLabel}" />
<Border
Grid.Row="7"
Grid.Row="10"
Background="{Binding
vega_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
@@ -743,7 +860,7 @@
<!-- Volume (label) -->
<TextBlock
Grid.Column="0"
Grid.Row="8"
Grid.Row="11"
Text="{Binding left_panel_width, Converter={StaticResource VolumeTextConverter}}"
Visibility="{Binding
volume_style.actual_show_label,
@@ -752,7 +869,7 @@
<!-- Volume (price) -->
<Border
Grid.Row="8"
Grid.Row="11"
Background="{Binding
volume_style.background_color,
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
@@ -793,6 +910,7 @@
Margin="2,10,2,2"
ItemsSource="{Binding accounts}"
SelectedItem="{Binding selected_account}"
ToolTip="Account"
Visibility="{Binding
DataContext.show_account,
RelativeSource={RelativeSource AncestorType=UserControl},
@@ -802,6 +920,7 @@
<ComboBox
DataContext="{Binding order_properties_vm}" ItemsSource="{Binding time_in_forces}"
SelectedItem="{Binding selected_time_in_force}" Margin="2"
ToolTip="Time In Force"
Visibility="{Binding
DataContext.show_tif,
RelativeSource={RelativeSource AncestorType=UserControl},
@@ -824,6 +943,7 @@
IsOpen="{Binding is_expanded}"
ItemsSource="{Binding algo_definitions_view}"
SelectedItem="{Binding selected_algo_definition}"
ToolTip="Order/Algo Type"
Visibility="{Binding
DataContext.show_order_type,
RelativeSource={RelativeSource AncestorType=UserControl},
@@ -879,6 +999,7 @@
Increment="{Binding size_calculator.unit_quantity}"
Minimum="0"
TickDisplaySettings="{Binding size_calculator.display_settings}"
ToolTip="Size"
Value="{Binding size_calculator.display_value, Mode=TwoWay}" />
<!-- Qty quick buttons -->
@@ -901,7 +1022,8 @@
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
Content="{Binding}"
Focusable="False"
Foreground="{u:BTSColor UI.Button.normal_foreground}">
Foreground="{u:BTSColor UI.Button.normal_foreground}"
ToolTip="Add to Size">
<Button.CommandParameter>
<MultiBinding
Converter="{x:Static cv:DynamicDoubleMultiplierConverter.Instance}">
@@ -921,6 +1043,7 @@
Margin="2"
HorizontalAlignment="Stretch"
Command="{Binding size_calculator.clear}"
ToolTip="Clear Size"
Visibility="{Binding
show_clear, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource CommonButton}">
@@ -931,6 +1054,7 @@
<CheckBox
Margin="2"
IsChecked="{Binding auto_clear}"
ToolTip="Clear Size after order"
Visibility="{Binding
show_auto_clear, Converter={StaticResource BooleanToVisibilityConverter}}">
Auto Clear
@@ -973,19 +1097,19 @@
<Button
Command="{Binding cancel_cmd}"
CommandParameter="Bids"
Style="{StaticResource {x:Static sp:LadderView.CancelBuysButtonStyleKey}}"
Style="{StaticResource CancelBuys}"
Visibility="{Binding
show_cancel_buys, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button
Command="{Binding cancel_cmd}"
CommandParameter="Asks"
Style="{StaticResource {x:Static sp:LadderView.CancelSellsButtonStyleKey}}"
Style="{StaticResource CancelSells}"
Visibility="{Binding
show_cancel_sells, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button
Command="{Binding cancel_cmd}"
CommandParameter="All"
Style="{StaticResource {x:Static sp:LadderView.CancelAllButtonStyleKey}}"
Style="{StaticResource CancelAll}"
Visibility="{Binding
show_cancel_all, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
@@ -27,22 +27,6 @@ namespace mbts.spark {
MinZoomFactor = 0.5,
MaxZoomFactor = 1.5;
static LadderView() {
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
MimicTT = knobs.MimicTt;
}
public static bool MimicTT { get; }
public static string CancelBuysButtonStyleKey =>
MimicTT ? "TT_CancelBuys" : "CancelBuys";
public static string CancelSellsButtonStyleKey =>
MimicTT ? "TT_CancelSells" : "CancelSells";
public static string CancelAllButtonStyleKey =>
MimicTT ? "TT_CancelAll" : "CancelAll";
public static IMultiValueConverter PositionToolTipConverter =
ValueConverter.create<AccountTuple?, bool, bool, bool, string>((account, show_day_pos, show_raw_day_pos, show_total_pos) => {
const string day_pos_text = "day", raw_day_pos_text = "raw day", total_pos_text = "total";
@@ -76,14 +60,8 @@ namespace mbts.spark {
if (show_total_pos) add_part(total_pos_text, " (", ")");
//
if (part_count > 1) {
// This is a complex expression (showing more than 1 value) - wrap it in single quotes.
// Explicitly state which value will be copied to the size input.
sb.Insert(0, "'");
sb.Add("'");
sb.Add(" position\n(click to copy ", primary_pos, " to size)");
} else {
// Only showing 1 value, no need for single quotes,
// nor an explicit statement that indicates which value will be copied to the size input.
sb.Add(" position\n(click to copy to size)");
}
return sb.ToString();
@@ -1,4 +1,4 @@
using bts;
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
@@ -223,6 +223,10 @@ namespace mbts.spark {
Add(qty_column = new SizeColumn(
new DoubleMinMax<OrderRecordTupleWithRoot>(
ort => ort.order.order.quantity(), nameof(OrderBookRowVM.quantity))));
// notional value
Add(notional_value_column = new NotionalValueColumn(
new DoubleMinMax<OrderRecordTupleWithRoot>(
ort => ort.order.order.notional_value(), nameof(OrderBookRowVM.notional_value))));
// edit quantity column
// not usually part of the main order book, but it's useful to add it
// rather than debugging the popup window
@@ -363,6 +367,7 @@ namespace mbts.spark {
public SimpleTextColumn<OrderSubType[]> sub_type_column { get; }
public SideColumn side_column { get; }
public SizeColumn qty_column { get; }
public NotionalValueColumn notional_value_column { get; }
public EditableQtyColumn edit_qty_column { get; }
public SimpleTextColumn<DoubleMinMaxState> price_column { get; }
public FilledColumn filled_qty_column { get; }
@@ -391,6 +396,7 @@ namespace mbts.spark {
order_sub_types = sub_type_column.filter.get_state(),
sides = side_column.filter.get_state(),
qty = qty_column.filter.get_state(),
notional_value = notional_value_column.filter.get_state(),
price = price_column.filter.get_state(),
qty_filled = filled_qty_column.filter.get_state(),
avg_price = avg_price_column.filter.get_state(),
@@ -415,6 +421,7 @@ namespace mbts.spark {
account_column.filter.load_state(fs.accounts);
price_column.filter.load_state(fs.price);
qty_column.filter.load_state(fs.qty);
notional_value_column.filter.load_state(fs.notional_value);
filled_qty_column.filter.load_state(fs.qty_filled);
side_column.filter.load_state(fs.sides);
order_type_column.filter.load_state(fs.order_types);
@@ -645,6 +652,25 @@ namespace mbts.spark {
DefaultCellPadding);
}
public class NotionalValueColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
public NotionalValueColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
: base(Columns.NotionalValue, DefaultColumnHeaders.NotionalValue, r => r.notional_value_text, DefaultColumnWidths.Qty, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(
dc,
_get_glyph_context(row),
font_size,
row.notional_value_text,
position,
row_height,
RenderUtils.get_side_brush(row),
background_brush,
horizontal_alignment,
VerticalAlignment.Center,
DefaultCellPadding);
}
public class FilledColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
public FilledColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
: base(Columns.Filled, "Filled Qty", r => r.display_filled_quantity.ToString(), DefaultColumnWidths.Qty, filter, DataGridFilterKnownTemplates.DoubleMinMax, _wrap_comparer(r => r.filled_quantity)) { }
@@ -1,4 +1,4 @@
using bts;
using bts;
using bts.api;
using bts.api.orderman;
using bts.collections;
@@ -108,6 +108,8 @@ namespace mbts.spark {
public double display_quantity => quantity.safe_divide(_display_qty_divisor);
public string display_quantity_text =>
quantity_display_settings.to_tick_display_settings().format(display_quantity);
public double notional_value => order.notional_value();
public string notional_value_text => _fmt_price(notional_value);
public TickDisplaySettingsImmutable quantity_display_settings {
get => _quantity_display_settings;
set {
@@ -389,7 +389,7 @@ namespace mbts.spark {
}
void refresh_quantity_display() {
foreach (var row in _row_collection.all_rows) {
foreach (var row in _row_collection.GetAllRows()) {
if (_cached_securities.TryGetValue(row.security_key, out var smd)) {
row.quantity_display_settings = smd.quantity_display;
}
@@ -41,6 +41,8 @@ namespace bts.shell.orderbook {
public Side[] sides;
[ProtoMember(10)]
public DoubleMinMaxState qty;
[ProtoMember(26)]
public DoubleMinMaxState notional_value;
[ProtoMember(11)]
public DoubleMinMaxState price;
[ProtoMember(12)]
@@ -83,6 +85,7 @@ namespace bts.shell.orderbook {
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
qty.safe_equals(other.qty) &&
notional_value.safe_equals(other.notional_value) &&
price.safe_equals(other.price) &&
qty_filled.safe_equals(other.qty_filled) &&
avg_price.safe_equals(other.avg_price) &&
@@ -111,6 +114,7 @@ namespace bts.shell.orderbook {
EqualityComparer.safe_aggregate_hash(root_algo_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
notional_value?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
@@ -149,6 +153,7 @@ namespace bts.shell.orderbook {
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
// numeric
pb.add_numeric(fs.qty, o => o.order.order.quantity());
pb.add_numeric(fs.notional_value, o => o.order.order.notional_value());
pb.add_price(fs.price, o => o.order.order.ui_price(), o => o.order.security);
pb.add_numeric(fs.qty_filled, o => o.order.order.filled_quantity());
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
@@ -172,6 +172,7 @@ namespace bts.shell.orderbook {
horizontal_alignment: HorizontalAlignment.Left);
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings notional_value => new GridColumnSettings(Columns.Size.ToString(), DIYGrid.DefaultColumnHeaders.NotionalValue, width: DIYGrid.DefaultColumnWidths.NotionalValue, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_quantity => new(
@@ -53,7 +53,8 @@ namespace mbts.spark {
Commit = 1 << 28,
RootAlgoType = 1 << 29,
Piq = 1 << 30,
Check = 1 << 31
Check = 1 << 31,
NotionalValue = 1 & (1 << 1),
}
public OrderBookGridView() {
@@ -18,7 +18,7 @@
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
<cv:NullableDoubleConverter x:Key="TickDefaultPriceConverter" DefaultValue="0"/>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Style TargetType="Button" BasedOn="{StaticResource DefaultButton}">
<Setter Property="Height" Value="{StaticResource ButtonHeight}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
@@ -53,8 +53,6 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140"/>
@@ -77,6 +75,7 @@
RegionalTick="{Binding price_tick}"
Focusable="false"
TickDefaultValue="{Binding current_price, Converter={StaticResource TickDefaultPriceConverter}, FallbackValue={u:Double 0}}"
ToolTip="Price"
Value="{Binding price}"
/>
<c:TickUpDownNullableDouble
@@ -88,6 +87,7 @@
Increment="{Binding size_calculator.unit_quantity}"
Minimum="0"
TickDisplaySettings="{Binding size_calculator.display_settings}"
ToolTip="Size"
Value="{Binding size_calculator.display_value}"
/>
</Grid>
@@ -97,13 +97,24 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Command="{Binding set_price_command}" CommandParameter="{Binding bbo.bid}" Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
<Button
Command="{Binding set_price_command}"
CommandParameter="{Binding bbo.bid}"
Foreground="{u:BTSColor MarketData.bid}"
FontWeight="Bold"
ToolTip="Bid Price">
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="bbo.bid"/>
<Binding Path="security.ContractSpec"/>
</MultiBinding>
</Button>
<Button Grid.Column="2" Command="{Binding set_price_command}" CommandParameter="{Binding bbo.ask}" Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
<Button
Grid.Column="2"
Command="{Binding set_price_command}"
CommandParameter="{Binding bbo.ask}"
Foreground="{u:BTSColor MarketData.ask}"
FontWeight="Bold"
ToolTip="Ask Price">
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="bbo.ask"/>
<Binding Path="security.ContractSpec"/>
@@ -120,7 +131,7 @@
Command="{Binding size_calculator.set_actual_value_command}"
CommandParameter="{Binding bbo.bid_size}"
Foreground="{u:BTSColor MarketData.bid}"
>
ToolTip="Bid Size">
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
<Binding Path="bbo.bid_size" />
<Binding Path="qty_display" />
@@ -131,20 +142,20 @@
Command="{Binding size_calculator.set_actual_value_command}"
CommandParameter="{Binding bbo.ask_size}"
Foreground="{u:BTSColor MarketData.ask}"
>
ToolTip="Ask Size">
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
<Binding Path="bbo.ask_size" />
<Binding Path="qty_display" />
</MultiBinding>
</Button>
</Grid>
<Grid Grid.Row="4" Grid.RowSpan="3">
<Grid Grid.Row="4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsControl Margin="-2,-2,-2,0" DataContext="{Binding size_calculator}" ItemsSource="{Binding buttons}" Focusable="False">
<ItemsControl Margin="0,0,-4,-4" DataContext="{Binding size_calculator}" ItemsSource="{Binding buttons}" Focusable="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" Focusable="False"/>
@@ -153,11 +164,11 @@
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Margin="2,4,2,0"
Margin="0,0,4,4"
Content="{Binding}"
Focusable="False"
Command="{Binding Path=DataContext.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
>
ToolTip="Add to Size">
<Button.CommandParameter>
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
<Binding />
@@ -170,35 +181,35 @@
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Grid.Row="2" Command="{Binding size_calculator.clear}">Clear</Button>
<Button
Grid.Row="2"
Command="{Binding size_calculator.clear}"
Content="Clear"
ToolTip="Clear Size" />
</Grid>
<Grid Grid.Row="4" Grid.Column="2" Grid.RowSpan="3">
<Grid Grid.Row="4" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox
Grid.ColumnSpan="2"
usp:OrderModificationControl.IsDirty="{Binding account_dirty, FallbackValue={u:False}}"
IsEnabled="{Binding can_change_account, FallbackValue={u:True}}"
ItemsSource="{Binding properties_vm.accounts}"
Margin="0,2"
SelectedItem="{Binding properties_vm.selected_account}"
/>
ToolTip="Account" />
<ComboBox
Grid.Row="1"
Grid.ColumnSpan="2"
Grid.Row="2"
usp:OrderModificationControl.IsDirty="{Binding time_in_force_dirty, FallbackValue={u:False}}"
IsEnabled="{Binding can_change_time_in_force, FallbackValue={u:True}}"
ItemsSource="{Binding properties_vm.time_in_forces}"
Margin="0,2"
SelectedItem="{Binding properties_vm.selected_time_in_force}"
/>
ToolTip="Time In Force" />
<uag:OrderDetailsPopupControl
Grid.Row="2"
Margin="0,2"
Grid.Row="4"
DataContext="{Binding properties_vm}"
usp:OrderModificationControl.IsDirty="{Binding DataContext.order_type_dirty, RelativeSource={RelativeSource AncestorType=usp:OrderModificationControl}, FallbackValue={u:False}}"
HasEditableProperties="{Binding can_expand, FallbackValue={u:False}}"
@@ -207,7 +218,7 @@
IsOpen="{Binding is_expanded, FallbackValue={u:False}}"
ItemsSource="{Binding algo_definitions_view}"
SelectedItem="{Binding selected_algo_definition}"
>
ToolTip="Order/Algo Type">
<uag:OrderDetailsPopupControl.Style>
<Style TargetType="uag:OrderDetailsPopupControl" BasedOn="{StaticResource {x:Type uag:OrderDetailsPopupControl}}">
<Style.Triggers>
@@ -128,7 +128,7 @@ namespace mbts.spark {
new PropertyValidation<AccountTuple?>(
a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account"))
});
attach(_accounts, accounts_src.Select(accs => accs.Values.ToList()), inline_account_src);
attach(_accounts, accounts_src.Select(accs => accs.Values.ToListSorted()), inline_account_src);
// time in force
_time_in_force = new SelectorProperty<TimeInForce>(
@@ -58,9 +58,10 @@ namespace BCG {
}
// TODO: put it as a setting
bts.Log.assert(theos.Length == drivers.Length);
for (int i = 0; i < theos.Length; i++) {
theos[i] /= drivers[i];
}
// only do this when its deribit crypto option, TODO
// for (int i = 0; i < theos.Length; i++) {
// theos[i] /= drivers[i];
// }
return new SpreadValue(spread_spec, theos, deltas, gammas, vegas, adj_vegas, oevs, thetas, slope_vegas, drivers, qs_midpoint_theos, pos, additional_delta);
}
}
@@ -227,7 +227,7 @@ namespace mbts.ui {
public static BTSColor atm_background => BTSCoreColors.gray_dark;
public static BTSColor session_low => 0xff59596b;
public static BTSColor session_low => 0xff8f5924;
public static BTSColor session_high => 0xff239b00;
public static BTSColor session_range_fill => 0x1a000000;
@@ -984,7 +984,7 @@ namespace mbts.ui {
public static BTSColor alternate_column_foreground => UI.foreground_standout;
public static BTSColor bid_line => BTSCoreColors.blue_light_dark_saturated;
public static BTSColor ask_line => BTSCoreColors.red_light_dark_saturated;
public static BTSColor mid_line => 0x40000000;
public static BTSColor mid_line => 0xffa4a4a4;
public static BTSColor default_column_background => 0xff141420;//0xff28283b;
public static BTSColor alternate_column_background => 0xff141420;// 0xff494957;
@@ -5,7 +5,8 @@ using mbts.ui.utility.text;
using ProtoBuf;
using System;
using Spark.Core.Protobuf;
using System.Windows;
using System.Windows.Media;
@@ -13,7 +14,7 @@ using FontFamily = System.Windows.Media.FontFamily;
namespace bts.proto {
[ProtoContract]
class FontParamsProto : IEquatable<FontParamsProto> {
class FontParamsProto : IProtobuf<FontParamsProto> {
[ProtoMember(1)]
public TypefaceProto typeface { get; set; }
[ProtoMember(2)]
@@ -24,6 +25,27 @@ namespace bts.proto {
public string font_family => typeface.font_family;
public int font_weight => typeface.font_weight;
public FontParamsProto With(TypefaceProto? typeface = null, double? font_size = null, uint? font_color = null, bool in_place = false) {
var rv = in_place ? this : Clone();
if (typeface.HasValue) {
rv.typeface = typeface.Value;
}
if (font_size.HasValue) {
rv.font_size = font_size.Value;
}
if (font_color.HasValue) {
rv.font_color = font_color.Value;
}
return rv;
}
public FontParamsProto Clone() =>
new() {
typeface = typeface.Clone(),
font_size = font_size,
font_color = font_color,
};
public bool Equals(FontParamsProto other) =>
other != null &&
typeface == other.typeface &&
@@ -4,14 +4,15 @@ using mbts.ui.utility.text;
using ProtoBuf;
using System;
using Spark.Core.Protobuf;
using System.Windows;
using FontFamily = System.Windows.Media.FontFamily;
namespace bts.proto {
[ProtoContract]
readonly struct TypefaceProto : IEquatable<TypefaceProto> {
readonly struct TypefaceProto : IProtobuf<TypefaceProto> {
static readonly int _type_hash = typeof(TypefaceProto).GetHashCode();
[ProtoMember(1)]
@@ -30,6 +31,9 @@ namespace bts.proto {
font_family ?? this.font_family,
font_weight ?? this.font_weight);
public TypefaceProto Clone() =>
new(font_family.ToString(), font_weight);
public bool Equals(TypefaceProto other) =>
font_family == other.font_family &&
font_weight == other.font_weight;
+9 -2
View File
@@ -379,8 +379,15 @@ namespace mbts.ui.controls {
class BadgeTextLinesConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (ConvertUtils.try_convert_args(values, out Side side, out uint quantity, out uint filled, out mbts.spark.LadderSettings.BadgeStyle style))
return BadgeRender.format(side, quantity, filled, style);
if (ConvertUtils.TryConvertArgs(
values,
out Side side,
out uint quantity,
out uint filled,
out double price,
out LadderSettings.BadgeStyle style)) {
return BadgeRender.format(side, quantity, filled, price, style);
}
else return new string[0];
}
@@ -1,4 +1,4 @@
namespace mbts.ui.controls {
namespace mbts.ui.controls {
using bts;
using bts.utils;
@@ -43,6 +43,7 @@
Side = 47,
Qty = 48,
MergedQty = 68,
NotionalValue = 68,
Filled = 56,
Symbol = 60,
Time = 112,
@@ -122,6 +123,7 @@
SecurityType = "Security Type",
Side = "B/S",
Qty = "Qty",
NotionalValue = "Notional",
MergedQty = "Qty",
DirectQty = "Qty (D)",
ImpliedQty = "Qty (I)",
@@ -473,7 +473,7 @@ namespace mbts.spark.filters {
// Exchange
public static IDataGridColumnPersistentFilter<TRow, Venue[]> exchange<TRow>(
Func<TRow, Venue> get_row_exchange, string? prop_name = null) =>
Create(EnumHelper.get_values_except(Venue.Unset).ToList(), get_row_exchange, prop_name);
Create(EnumHelper.get_values_except(Venue.Unset).ToListSorted(e => e.ToString()), get_row_exchange, prop_name);
// Symbol
public static IDataGridColumnPersistentFilter<TRow, string[]> symbol<TRow>(
@@ -12,7 +12,10 @@ namespace mbts.spark.filters {
str = str.Trim();
if (str == string.Empty)
return null;
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
try {
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
} catch (RegexParseException) { }
return null;
}
}
class RegexFilterVM<TRow> : FilterVMBase<TRow, string, string> {
@@ -3,6 +3,7 @@
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
>
<UserControl.Resources>
@@ -21,7 +22,7 @@
<GroupBox Header="Font Family" Height="{Binding ActualHeight, ElementName=_panel_right}">
<ListBox
x:Name="_font_family_list_box"
Background="White"
Background="{u:BTSBrush UI.ListBox.normal_background}"
ItemsSource="{Binding FontFamilies, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
SelectedItem="{Binding SelectedFontFamily, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
>
@@ -40,7 +41,7 @@
</Grid.RowDefinitions>
<GroupBox Header="Font Weight">
<ListBox
Background="White"
Background="{u:BTSBrush UI.ListBox.normal_background}"
ItemsSource="{Binding FontWeights, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
SelectedItem="{Binding SelectedFontWeight, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
>
@@ -73,7 +74,7 @@
FontFamily="{Binding SelectedFontFamily, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
FontSize="{Binding SelectedFontSize, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
FontWeight="{Binding SelectedFontWeight, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
Foreground="{Binding SelectedFontColor, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}, Converter={StaticResource ColorToBrushConverter}}"
Foreground="{u:BTSBrush UI.default_text}"
TextWrapping="Wrap"
>
<TextBlock.Inlines>
@@ -12,8 +12,12 @@
x:Name="order_type"
Text="{Binding algo_root.order_type, Converter={StaticResource OrderTypeDisplayTextConverter}, StringFormat='{}{0}: ', Mode=OneWay}"
FontWeight="Bold"
/>
<TextBlock x:Name="display_name" Text="{Binding name, Mode=OneWay}" FontWeight="Normal"/>
Foreground="{u:BTSBrush UI.default_text}" />
<TextBlock
x:Name="display_name"
Text="{Binding name, Mode=OneWay}"
FontWeight="Normal"
Foreground="{u:BTSBrush UI.default_text}" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Value="{u:True}">
@@ -87,10 +87,10 @@
</c:TickUpDown>
</ControlTemplate>
<Style TargetType="usp:PriceEntryControl">
<Setter Property="Background" Value="{u:BTSColor UI.input_background}" />
<Setter Property="Background" Value="{u:BTSBrush UI.Button.normal_background}" />
<Setter Property="Foreground" Value="{u:BTSColor UI.input_text}" />
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Template" Value="{StaticResource PriceEntryTemplate}"/>
</Style>
@@ -14,7 +14,7 @@
<Style TargetType="{x:Type c:TickUpDown}">
<Setter Property="Background" Value="{StaticResource TextBox.Static.Background.Brush}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Height" Value="20" />
@@ -75,7 +75,8 @@
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource TextBox.MouseOver.Border.Brush}"/>
<Setter TargetName="Bd" Property="Background" Value="{u:BTSBrush UI.Button.mouseover_background}"/>
<Setter TargetName="Bd" Property="BorderBrush" Value="{u:BTSBrush UI.Button.mouseover_border}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -133,7 +134,8 @@
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource TextBox.MouseOver.Border.Brush}"/>
<Setter TargetName="Bd" Property="Background" Value="{u:BTSBrush UI.Button.mouseover_background}"/>
<Setter TargetName="Bd" Property="BorderBrush" Value="{u:BTSBrush UI.Button.mouseover_border}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -145,7 +147,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -188,7 +190,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -230,7 +232,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -272,7 +274,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -310,7 +312,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -348,7 +350,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -384,7 +386,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -428,7 +430,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -466,7 +468,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -548,7 +550,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -586,7 +588,7 @@
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="IsTabStop" Value="False"/>
@@ -557,6 +557,8 @@
</Style.Triggers>
</Style>
<Style x:Key="DefaultButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}" />
<Style TargetType="RepeatButton">
<Setter Property="Foreground" Value="{u:BTSBrush UI.Button.normal_foreground}" />
<Setter Property="Background" Value="{u:BTSBrush UI.Button.normal_background}" />
+1 -1
View File
@@ -100,7 +100,7 @@
}
void PerformMEFComposition(string config_source) {
Log.Ui.info($"Generating MEF dependencies for the shell " +
Log.Ui.info($"this is v2.2.3 Generating MEF dependencies for the shell" +
$"({typeof(IInitializable)}s, {typeof(IShellModule)}s, " +
$"{typeof(IShellDaemon)}s, etc.)");
try {
@@ -47,16 +47,16 @@ namespace mbts.spark {
readonly SecurityImmutable _security;
readonly MBOSecurityQueryImmutable _query;
readonly Dictionary<(string, bool), FormattedText> _cache;
readonly Func<int, string> _fmt_mbo_qty;
readonly Func<double, double, string> _fmt_mbo_qty;
int _max_order_size = 1;
double _height = 0;
double _number_width = 0;
public MBOPopupModel(MBOService mbo, SecurityImmutable security, MBOSecurityQueryImmutable query, Func<int, string> fmt_mbo_qty) {
public MBOPopupModel(MBOService mbo, SecurityImmutable security, MBOSecurityQueryImmutable query, Func<double, double, string> fmt_mbo_qty) {
_mbo = mbo;
_security = security;
_query = query;
_fmt_mbo_qty = fmt_mbo_qty ?? new Func<int, string>(v => v.ToString());
_fmt_mbo_qty = fmt_mbo_qty ?? new Func<double, double, string>((v, p) => v.ToString());
// prime the cache with numbers on the left hand side
_cache =
@@ -112,7 +112,7 @@ namespace mbts.spark {
pdp.row_numbers = p.orders.Select((o, i) => format_text((i + 1).ToString(), ismine(o))).ToList();
pdp.numbers_width = _number_width = Math.Max(_number_width, (int)pdp.row_numbers.Max(r => r.Width) + text_margin);
pdp.row_labels = p.orders.Select(o => format_text(_fmt_mbo_qty(o.Size) + (ismine(o) ? "*" : ""), ismine(o))).ToList();
pdp.row_labels = p.orders.Select(o => format_text(_fmt_mbo_qty(o.Size, o.Price) + (ismine(o) ? "*" : ""), ismine(o))).ToList();
var max_label_width = pdp.row_labels.Max(l => l.Width) + text_margin;
var available_width = pdp.width - pdp.numbers_width - max_label_width;
+401 -335
View File
@@ -1,335 +1,401 @@
using bts.api;
using bts.api.domain;
using bts.utils;
using mbts.co2;
using mbts.spark;
using mbts.ui.shell;
using mbts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace mbts.ui.spark {
[ProtoContract]
class MarketSizeDisplay : IEquatable<MarketSizeDisplay> {
[ProtoMember(1)]
public readonly SizeFormattingElement[] normal;
[ProtoMember(2)]
public readonly SizeFormattingElement[] implied_present;
public MarketSizeDisplay() { }
public MarketSizeDisplay(SizeFormattingElement[] normal, SizeFormattingElement[] implied_present) {
this.normal = normal;
this.implied_present = implied_present;
}
public enum SizeFormattingElementType {
Merged,
Direct,
Implied,
Const
}
public static MarketSizeDisplay get_default() =>
new MarketSizeDisplay(SizeFormattingElement.merged, SizeFormattingElement.merged_asterisk);
public MarketSizeDisplay with(SizeFormattingElement[] normal = null, SizeFormattingElement[] implied_present = null) =>
new MarketSizeDisplay(normal ?? this.normal, implied_present ?? this.implied_present);
public bool Equals(MarketSizeDisplay other) =>
other != null &&
EqualityComparer.safe_sequence_equals(normal, other.normal) &&
EqualityComparer.safe_sequence_equals(implied_present, other.implied_present);
public override bool Equals(object obj) =>
Equals(obj as MarketSizeDisplay);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(implied_present.GetHashCode(), normal.GetHashCode());
[ProtoContract]
public struct SizeFormattingElement : IEquatable<SizeFormattingElement> {
[ProtoMember(1)]
public readonly SizeFormattingElementType type;
[ProtoMember(2)]
public readonly string data;
SizeFormattingElement(SizeFormattingElementType type, string data = null) {
this.type = type;
this.data = data;
}
public bool Equals(SizeFormattingElement other) => other.type == type && string.Equals(data, other.data);
public override bool Equals(object obj) => obj is SizeFormattingElement other && Equals(other);
public override int GetHashCode() => EqualityComparer.aggregate_hash(type.GetHashCode(), EqualityComparer.safe_get_hash_code(data));
public override string ToString() => type == SizeFormattingElementType.Const ? data ?? string.Empty : type.ToString();
public static bool operator ==(SizeFormattingElement a, SizeFormattingElement b) => a.Equals(b);
public static bool operator !=(SizeFormattingElement a, SizeFormattingElement b) => !a.Equals(b);
public static SizeFormattingElement[] direct => new[] { new SizeFormattingElement(SizeFormattingElementType.Direct) };
public static SizeFormattingElement[] implied => new[] { new SizeFormattingElement(SizeFormattingElementType.Implied) };
public static SizeFormattingElement[] direct_implied => new[] {
new SizeFormattingElement(SizeFormattingElementType.Direct),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Implied),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged) };
public static SizeFormattingElement[] merged_asterisk => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged), new SizeFormattingElement(SizeFormattingElementType.Const, "*") };
public static SizeFormattingElement[] merged_implied => new[] {
new SizeFormattingElement(SizeFormattingElementType.Merged),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Implied),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged_direct => new[] {
new SizeFormattingElement(SizeFormattingElementType.Merged),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Direct),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged_direct_implied => new[] {
new SizeFormattingElement(SizeFormattingElementType.Merged),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Direct),
new SizeFormattingElement(SizeFormattingElementType.Const, ":"),
new SizeFormattingElement(SizeFormattingElementType.Implied),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
}
}
static class MDDisplayHelper {
/// This delegate should be created by calling <see cref="get_formatter(MarketSizeDisplay)"/>
public delegate string SizeFormatter(double merged, double implied);
/// The private delegates below use refs bc the <see cref="SizeFormatter"/> returned by <see cref="get_formatter(MarketSizeDisplay)"/>
/// internally uses several <see cref="_SizeFormatter"/>s to add different text components to the resultant string.
/// The refs, therefor, avoid making extra copies of the variables.
/// These are private members, so any code-readability cost is confined to a limited scope.
delegate void _SizeFormatter(ref double merged, ref double implied, ref StringBuilder sb);
delegate void _SizeFormatterNPS(ref NativePriceSize nps, ref StringBuilder sb);
public static Func<double, string> get_value_formatter(double divisor) =>
value => (value / divisor).ToString();
public static Func<double, string> get_value_formatter(
TickDisplaySettingsImmutable display) =>
v => display.format(v);
public static Func<NativePriceSize, string> get_formatter_nps(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display) {
var raw_fmt_size = get_formatter(size_display, display);
return nps => raw_fmt_size(
nps.implied_size + nps.direct_size,
nps.implied_size);
}
public static SizeFormatter get_formatter(MarketSizeDisplay size_display, TickDisplaySettingsImmutable display) {
_SizeFormatter implied_absent = _get_formatter(size_display.normal, display);
_SizeFormatter implied_present = _get_formatter(
size_display.implied_present ?? size_display.normal, display);
return (merged, implied) => {
StringBuilder sb = new StringBuilder();
(implied > 0 ? implied_present : implied_absent)(ref merged, ref implied, ref sb);
return sb.ToString();
};
}
static _SizeFormatterNPS _get_formatter_nps(
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
TickDisplaySettingsImmutable display) {
var formatters = fmt_elements.is_null_or_empty() ?
Array.Empty<_SizeFormatterNPS>() :
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter_nps(sfe, display));
return new _SizeFormatterNPS((ref NativePriceSize nps, ref StringBuilder sb) => {
foreach (var f in formatters)
f(ref nps, ref sb);
});
}
static _SizeFormatter _get_formatter(
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
TickDisplaySettingsImmutable display) {
_SizeFormatter[] formatters = fmt_elements.is_null_or_empty() ?
Array.Empty<_SizeFormatter>() :
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter(sfe, display));
return new _SizeFormatter((ref double m, ref double i, ref StringBuilder sb) => {
foreach (var f in formatters)
f(ref m, ref i, ref sb);
});
}
static _SizeFormatterNPS _get_formatter_nps(
MarketSizeDisplay.SizeFormattingElement fmt_element,
TickDisplaySettingsImmutable display) =>
fmt_element.type switch {
MarketSizeDisplay.SizeFormattingElementType.Const =>
SizeTokenFormatters.char_formatter_nps(fmt_element.data ?? string.Empty),
MarketSizeDisplay.SizeFormattingElementType.Direct =>
SizeTokenFormatters.direct_formatter_nps(display),
MarketSizeDisplay.SizeFormattingElementType.Implied =>
SizeTokenFormatters.implied_formatter_nps(display),
MarketSizeDisplay.SizeFormattingElementType.Merged =>
SizeTokenFormatters.merged_formatter_nps(display),
_ => throw ExceptionBuilder.Argument.unexpected_case(
fmt_element.type, nameof(fmt_element.type)),
};
static _SizeFormatter _get_formatter(
MarketSizeDisplay.SizeFormattingElement fmt_element,
TickDisplaySettingsImmutable display) =>
fmt_element.type switch {
MarketSizeDisplay.SizeFormattingElementType.Const =>
SizeTokenFormatters.char_formatter(fmt_element.data ?? string.Empty),
MarketSizeDisplay.SizeFormattingElementType.Direct =>
SizeTokenFormatters.direct_formatter(display),
MarketSizeDisplay.SizeFormattingElementType.Implied =>
SizeTokenFormatters.implied_formatter(display),
MarketSizeDisplay.SizeFormattingElementType.Merged =>
SizeTokenFormatters.merged_formatter(display),
_ => throw ExceptionBuilder.Argument.unexpected_case(
fmt_element.type, nameof(fmt_element.type)),
};
static class SizeTokenFormatters {
public static _SizeFormatter char_formatter(string data) {
string local = data.ToString();
return new _SizeFormatter((ref double m, ref double i, ref StringBuilder sb) => sb.Append(local));
}
public static _SizeFormatter merged_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref StringBuilder sb) => sb.Append(display.format(m));
public static _SizeFormatter direct_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref StringBuilder sb) => sb.Append(display.format(m - i));
public static _SizeFormatter implied_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref StringBuilder sb) => sb.Append(display.format(i));
public static _SizeFormatterNPS char_formatter_nps(string data) {
string local = data.ToString();
return new _SizeFormatterNPS((ref NativePriceSize _, ref StringBuilder sb) => sb.Append(local));
}
public static _SizeFormatterNPS merged_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format((double)nps.size));
public static _SizeFormatterNPS direct_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format((double)nps.direct_size));
public static _SizeFormatterNPS implied_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format((double)nps.implied_size));
}
}
class MarketSizeDisplayVM : ViewModelBase, IValueFactory<MarketSizeDisplay> {
readonly IEqualityComparer<MarketSizeDisplay.SizeFormattingElement[]> _comp = EqualityComparer.Create<MarketSizeDisplay.SizeFormattingElement[]>((a, b) => EqualityComparer.safe_sequence_equals(a, b));
readonly Property<FormattingVM> _selected_format, _selected_alternate_implied_format;
readonly DelegateProperty<bool> _can_choose_alternate_implied_format;
public MarketSizeDisplayVM() {
_selected_format = backing<FormattingVM>(nameof(selected_format));
_selected_alternate_implied_format = backing<FormattingVM>(nameof(selected_alternate_implied_format));
_can_choose_alternate_implied_format = backing(nameof(can_choose_alternate_implied_format), () =>
_comp.Equals(MarketSizeDisplay.SizeFormattingElement.merged, selected_format?.value));
sample_items = new[] {
new SampleItem(90, 0, this),
new SampleItem(75, 25, this)
};
_selected_format.Changed += refresh_sample_items;
_selected_alternate_implied_format.Changed += refresh_sample_items;
void refresh_sample_items(FormattingVM oldval, FormattingVM newval) {
_can_choose_alternate_implied_format.refresh();
foreach (var si in sample_items)
si.refresh();
}
}
public IEnumerable<FormattingVM> format_options { get; } = new[] {
MarketSizeDisplay.SizeFormattingElement.direct,
MarketSizeDisplay.SizeFormattingElement.implied,
MarketSizeDisplay.SizeFormattingElement.direct_implied,
MarketSizeDisplay.SizeFormattingElement.merged_implied,
MarketSizeDisplay.SizeFormattingElement.merged_direct_implied,
MarketSizeDisplay.SizeFormattingElement.merged
}.Select(ts => new FormattingVM(ts)).ToArray();
public IEnumerable<FormattingVM> alternate_implied_format_options { get; } = new[] {
MarketSizeDisplay.SizeFormattingElement.merged,
MarketSizeDisplay.SizeFormattingElement.merged_asterisk,
MarketSizeDisplay.SizeFormattingElement.merged_implied,
MarketSizeDisplay.SizeFormattingElement.merged_direct,
MarketSizeDisplay.SizeFormattingElement.merged_direct_implied
}.Select(ts => new FormattingVM(ts)).ToArray();
public FormattingVM selected_format {
get => _selected_format.Value;
set => _selected_format.Value = value;
}
public FormattingVM selected_alternate_implied_format {
get => _selected_alternate_implied_format.Value;
set => _selected_alternate_implied_format.Value = value;
}
public bool can_choose_alternate_implied_format => _can_choose_alternate_implied_format.Value;
public IEnumerable<SampleItem> sample_items { get; }
public MarketSizeDisplay create() {
var normal_fmt = selected_format?.value;
var alt_implied_fmt = can_choose_alternate_implied_format ? selected_alternate_implied_format?.value : normal_fmt;
return new MarketSizeDisplay(normal_fmt, alt_implied_fmt);
}
public void load_from(MarketSizeDisplay msd) {
selected_format = format_options.FirstOrDefault(f => _comp.Equals(f.value, msd.normal));
if (can_choose_alternate_implied_format)
selected_alternate_implied_format = alternate_implied_format_options.FirstOrDefault(f => _comp.Equals(f.value, msd.implied_present));
}
public class FormattingVM : ViewModelBase, IEquatable<FormattingVM> {
public FormattingVM(MarketSizeDisplay.SizeFormattingElement[] tokens) =>
value = tokens;
public MarketSizeDisplay.SizeFormattingElement[] value { get; }
public bool Equals(FormattingVM other) =>
other != null &&
EqualityComparer.safe_sequence_equals(value, other.value);
public override bool Equals(object obj) => Equals(obj as FormattingVM);
public override int GetHashCode() => EqualityComparer.safe_aggregate_hash(value);
public override string ToString() => string.Join(string.Empty, value);
}
public class SampleItem : ViewModelBase {
readonly DelegateProperty<string> _display;
public SampleItem(uint direct, uint implied, MarketSizeDisplayVM owner) {
this.direct = direct;
this.implied = implied;
_display = backing(nameof(display), fmt);
string fmt() {
var impl = MDDisplayHelper.get_formatter(
owner.create(), Security.Defaults.quantity_display_settingsi);
return impl(merged, implied);
}
}
public uint direct { get; }
public uint implied { get; }
public uint merged => direct + implied;
public string display => _display.Value;
public void refresh() => _display.refresh();
}
}
}
using bts.api;
using bts.api.domain;
using bts.utils;
using mbts.co2;
using mbts.spark;
using mbts.ui.shell;
using mbts.utils;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace mbts.ui.spark {
[ProtoContract]
class MarketSizeDisplay : IEquatable<MarketSizeDisplay> {
[ProtoMember(1)]
public readonly SizeFormattingElement[] normal;
[ProtoMember(2)]
public readonly SizeFormattingElement[] implied_present;
public MarketSizeDisplay() { }
public MarketSizeDisplay(SizeFormattingElement[] normal, SizeFormattingElement[] implied_present) {
this.normal = normal;
this.implied_present = implied_present;
}
public enum SizeFormattingElementType {
Merged,
Direct,
Implied,
Const,
MergedNotional,
}
public static MarketSizeDisplay get_default() =>
new MarketSizeDisplay(SizeFormattingElement.merged, SizeFormattingElement.merged_asterisk);
public static bool TryParse(string expression, out List<SizeFormattingElement> elements) {
elements = new List<SizeFormattingElement>();
if (expression is null) {
return false;
}
int tokenStart = 0;
for (int i = 0; i < expression.Length - 3; i++) {
if (expression[i] != '$' || expression[i + 1] != '{') {
continue;
}
if (i > tokenStart) {
elements.Add(SizeFormattingElement.Create(expression.Substring(tokenStart, i - tokenStart)));
}
i += 2;
tokenStart = i;
while (i < expression.Length && expression[i] != '}') {
i++;
}
if (i == expression.Length
|| !Enum.TryParse(expression.Substring(tokenStart, i - tokenStart), out SizeFormattingElementType typ)
|| typ == SizeFormattingElementType.Const) {
elements = null;
return false;
}
elements.Add(SizeFormattingElement.Create(typ));
tokenStart = i + 1;
}
if (tokenStart < expression.Length) {
elements.Add(SizeFormattingElement.Create(expression.Substring(tokenStart)));
}
return true;
}
public MarketSizeDisplay with(SizeFormattingElement[] normal = null, SizeFormattingElement[] implied_present = null) =>
new MarketSizeDisplay(normal ?? this.normal, implied_present ?? this.implied_present);
public bool Equals(MarketSizeDisplay other) =>
other != null &&
EqualityComparer.safe_sequence_equals(normal, other.normal) &&
EqualityComparer.safe_sequence_equals(implied_present, other.implied_present);
public override bool Equals(object obj) =>
Equals(obj as MarketSizeDisplay);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(implied_present.GetHashCode(), normal.GetHashCode());
[ProtoContract]
public struct SizeFormattingElement : IEquatable<SizeFormattingElement> {
[ProtoMember(1)]
public readonly SizeFormattingElementType type;
[ProtoMember(2)]
public readonly string data;
SizeFormattingElement(SizeFormattingElementType type, string data = null) {
this.type = type;
this.data = data;
}
internal static SizeFormattingElement Create(string str) =>
new(SizeFormattingElementType.Const, str);
internal static SizeFormattingElement Create(SizeFormattingElementType type) =>
new(type);
public bool Equals(SizeFormattingElement other) => other.type == type && string.Equals(data, other.data);
public override bool Equals(object obj) => obj is SizeFormattingElement other && Equals(other);
public override int GetHashCode() => EqualityComparer.aggregate_hash(type.GetHashCode(), EqualityComparer.safe_get_hash_code(data));
public override string ToString() =>
type == SizeFormattingElementType.Const
? data ?? string.Empty
: "${" + type.ToString() + "}";
public static bool operator ==(SizeFormattingElement a, SizeFormattingElement b) => a.Equals(b);
public static bool operator !=(SizeFormattingElement a, SizeFormattingElement b) => !a.Equals(b);
public static SizeFormattingElement[] direct => new[] { new SizeFormattingElement(SizeFormattingElementType.Direct) };
public static SizeFormattingElement[] implied => new[] { new SizeFormattingElement(SizeFormattingElementType.Implied) };
public static SizeFormattingElement[] direct_implied => new[] {
new SizeFormattingElement(SizeFormattingElementType.Direct),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Implied),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged) };
public static SizeFormattingElement[] merged_asterisk => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged), new SizeFormattingElement(SizeFormattingElementType.Const, "*") };
public static SizeFormattingElement[] merged_implied => new[] {
new SizeFormattingElement(SizeFormattingElementType.Merged),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Implied),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged_direct => new[] {
new SizeFormattingElement(SizeFormattingElementType.Merged),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Direct),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged_direct_implied => new[] {
new SizeFormattingElement(SizeFormattingElementType.Merged),
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
new SizeFormattingElement(SizeFormattingElementType.Direct),
new SizeFormattingElement(SizeFormattingElementType.Const, ":"),
new SizeFormattingElement(SizeFormattingElementType.Implied),
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
};
public static SizeFormattingElement[] merged_notional => new SizeFormattingElement[] {
new(SizeFormattingElementType.MergedNotional)
};
}
}
static class MDDisplayHelper {
/// This delegate should be created by calling <see cref="get_formatter(MarketSizeDisplay)"/>
public delegate string SizeFormatter(double merged, double implied, double price, double notional);
/// The private delegates below use refs bc the <see cref="SizeFormatter"/> returned by <see cref="get_formatter(MarketSizeDisplay)"/>
/// internally uses several <see cref="_SizeFormatter"/>s to add different text components to the resultant string.
/// The refs, therefor, avoid making extra copies of the variables.
/// These are private members, so any code-readability cost is confined to a limited scope.
delegate void _SizeFormatter(ref double merged, ref double implied, ref double price, ref double notional, ref StringBuilder sb);
delegate void _SizeFormatterNPS(ref NativePriceSize nps, ref StringBuilder sb);
public static Func<double, double, string> get_value_formatter(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable notional_value_display) {
var raw_fmt_size = get_formatter(size_display, display, notional_value_display);
return (q, p) => raw_fmt_size(q, 0, p, double.NaN);
}
public static Func<LadderPriceLevel, string> get_formatter_lpl(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable notional_value_display) {
var raw_fmt_size = get_formatter(size_display, display, notional_value_display);
return nps => raw_fmt_size(
nps.Volume,
0,
nps.Price,
nps.NotionalValue);
}
public static Func<NativePriceSize, string> get_formatter_nps(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable notional_value_display) {
var raw_fmt_size = get_formatter(size_display, display, notional_value_display);
return nps => raw_fmt_size(
nps.implied_size + nps.direct_size,
nps.implied_size,
nps.price,
nps.notional_size);
}
public static SizeFormatter get_formatter(
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable notional_value_display) {
_SizeFormatter implied_absent = _get_formatter(size_display.normal, display, notional_value_display);
_SizeFormatter implied_present = _get_formatter(
size_display.implied_present ?? size_display.normal, display, notional_value_display);
return (merged, implied, price, notional) => {
StringBuilder sb = new StringBuilder();
(implied > 0 ? implied_present : implied_absent)(ref merged, ref implied, ref price, ref notional, ref sb);
return sb.ToString();
};
}
static _SizeFormatterNPS _get_formatter_nps(
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
TickDisplaySettingsImmutable display,
TickDisplaySettingsImmutable price_display) {
var formatters = fmt_elements.is_null_or_empty() ?
Array.Empty<_SizeFormatterNPS>() :
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter_nps(sfe, display, price_display));
return new _SizeFormatterNPS((ref NativePriceSize nps, ref StringBuilder sb) => {
foreach (var f in formatters)
f(ref nps, ref sb);
});
}
static _SizeFormatter _get_formatter(
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
TickDisplaySettingsImmutable display,
TickDisplaySettingsImmutable notional_value_display) {
_SizeFormatter[] formatters = fmt_elements.is_null_or_empty() ?
Array.Empty<_SizeFormatter>() :
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter(sfe, display, notional_value_display));
return new _SizeFormatter((ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) => {
foreach (var f in formatters)
f(ref m, ref i, ref p, ref n, ref sb);
});
}
static _SizeFormatterNPS _get_formatter_nps(
MarketSizeDisplay.SizeFormattingElement fmt_element,
TickDisplaySettingsImmutable display,
TickDisplaySettingsImmutable price_display) =>
fmt_element.type switch {
MarketSizeDisplay.SizeFormattingElementType.Const =>
SizeTokenFormatters.char_formatter_nps(fmt_element.data ?? string.Empty),
MarketSizeDisplay.SizeFormattingElementType.Direct =>
SizeTokenFormatters.direct_formatter_nps(display),
MarketSizeDisplay.SizeFormattingElementType.Implied =>
SizeTokenFormatters.implied_formatter_nps(display),
MarketSizeDisplay.SizeFormattingElementType.Merged =>
SizeTokenFormatters.merged_formatter_nps(display),
MarketSizeDisplay.SizeFormattingElementType.MergedNotional =>
SizeTokenFormatters.merged_notional_formatter_nps(price_display),
_ => throw ExceptionBuilder.Argument.unexpected_case(
fmt_element.type, nameof(fmt_element.type)),
};
static _SizeFormatter _get_formatter(
MarketSizeDisplay.SizeFormattingElement fmt_element,
TickDisplaySettingsImmutable display,
TickDisplaySettingsImmutable notional_value_display) =>
fmt_element.type switch {
MarketSizeDisplay.SizeFormattingElementType.Const =>
SizeTokenFormatters.char_formatter(fmt_element.data ?? string.Empty),
MarketSizeDisplay.SizeFormattingElementType.Direct =>
SizeTokenFormatters.direct_formatter(display),
MarketSizeDisplay.SizeFormattingElementType.Implied =>
SizeTokenFormatters.implied_formatter(display),
MarketSizeDisplay.SizeFormattingElementType.Merged =>
SizeTokenFormatters.merged_formatter(display),
MarketSizeDisplay.SizeFormattingElementType.MergedNotional =>
SizeTokenFormatters.merged_notional_formatter(notional_value_display),
_ => throw ExceptionBuilder.Argument.unexpected_case(
fmt_element.type, nameof(fmt_element.type)),
};
static class SizeTokenFormatters {
public static _SizeFormatter char_formatter(string data) {
string local = data.ToString();
return (ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
sb.Append(local);
}
public static _SizeFormatter merged_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
sb.Append(display.format(m));
public static _SizeFormatter direct_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
sb.Append(display.format(m - i));
public static _SizeFormatter implied_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
sb.Append(display.format(i));
public static _SizeFormatter merged_notional_formatter(TickDisplaySettingsImmutable display) =>
(ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
sb.Append(display.format(n));
public static _SizeFormatterNPS char_formatter_nps(string data) {
string local = data.ToString();
return new _SizeFormatterNPS((ref NativePriceSize _, ref StringBuilder sb) => sb.Append(local));
}
public static _SizeFormatterNPS merged_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format((double)nps.size));
public static _SizeFormatterNPS direct_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format((double)nps.direct_size));
public static _SizeFormatterNPS implied_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format((double)nps.implied_size));
public static _SizeFormatterNPS merged_notional_formatter_nps(TickDisplaySettingsImmutable display) =>
(ref NativePriceSize nps, ref StringBuilder sb) =>
sb.Append(display.format(nps.notional_size));
}
}
class MarketSizeDisplayVM : ViewModelBase, IValueFactory<MarketSizeDisplay> {
readonly IEqualityComparer<MarketSizeDisplay.SizeFormattingElement[]> _comp = EqualityComparer.Create<MarketSizeDisplay.SizeFormattingElement[]>((a, b) => EqualityComparer.safe_sequence_equals(a, b));
readonly Property<string> _selected_format, _selected_alternate_implied_format;
public MarketSizeDisplayVM() {
_selected_format = backing<string>(nameof(selected_format));
_selected_alternate_implied_format = backing<string>(nameof(selected_alternate_implied_format));
sample_items = new[] {
new SampleItem(90, 0, this),
new SampleItem(75, 25, this)
};
_selected_format.Changed += refresh_sample_items;
_selected_alternate_implied_format.Changed += refresh_sample_items;
void refresh_sample_items(string oldval, string newval) {
foreach (var si in sample_items)
si.refresh();
}
}
public IEnumerable<string> format_options { get; } = new[] {
string.Concat(MarketSizeDisplay.SizeFormattingElement.direct),
string.Concat(MarketSizeDisplay.SizeFormattingElement.implied),
string.Concat(MarketSizeDisplay.SizeFormattingElement.direct_implied),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_implied),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_direct_implied),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_notional),
};
public IEnumerable<string> alternate_implied_format_options { get; } = new[] {
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_asterisk),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_implied),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_direct),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_direct_implied),
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_notional),
};
public string selected_format {
get => _selected_format.Value;
set => _selected_format.Value = value;
}
public string selected_alternate_implied_format {
get => _selected_alternate_implied_format.Value;
set => _selected_alternate_implied_format.Value = value;
}
public IEnumerable<SampleItem> sample_items { get; }
public MarketSizeDisplay create() {
var normal_fmt = selected_format;
var alt_implied_fmt = selected_alternate_implied_format;
if (!MarketSizeDisplay.TryParse(normal_fmt, out var normal_els)) {
normal_els = new(MarketSizeDisplay.SizeFormattingElement.merged);
}
if (!MarketSizeDisplay.TryParse(alt_implied_fmt, out var alt_implied_els)) {
alt_implied_els = new(normal_els);
}
return new MarketSizeDisplay(normal_els.ToArray(), alt_implied_els.ToArray());
}
public void load_from(MarketSizeDisplay msd) {
selected_format = string.Concat(msd.normal.empty_if_null());
selected_alternate_implied_format = string.Concat(msd.implied_present.empty_if_null());
}
public class SampleItem : ViewModelBase {
readonly DelegateProperty<string> _display;
public SampleItem(uint direct, uint implied, MarketSizeDisplayVM owner) {
this.direct = direct;
this.implied = implied;
_display = backing(nameof(display), fmt);
string fmt() {
const double SAMPLE_PRICE = 25.0;
var impl = MDDisplayHelper.get_formatter(
owner.create(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
return impl(merged, implied, 1, merged * SAMPLE_PRICE);
}
}
public uint direct { get; }
public uint implied { get; }
public uint merged => direct + implied;
public string display => _display.Value;
public void refresh() => _display.refresh();
}
}
}
@@ -41,6 +41,7 @@ namespace mbts.co2 {
public Size size => _size;
public Size direct_size => _direct_size;
public Size implied_size => _size - _direct_size;
public double notional_size => price * size;
public double price => _price.price();
public bool is_nan => _price.is_nan;
@@ -11,6 +11,7 @@ namespace mbts {
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
public readonly struct SecurityMD : IEquatable<SecurityMD> {
public static readonly Func<SecurityMD, uint> key_fn = smd => smd.security.Id;
@@ -60,4 +61,128 @@ namespace mbts {
public static bool operator !=(SecurityMD left, SecurityMD right) =>
!(left == right);
}
static class SecurityMDExtensions {
const uint DEPTH_COUNT = 10;
[ThreadStatic]
static NativePriceSize[] leg1Bids;
[ThreadStatic]
static NativePriceSize[] leg1Asks;
static SecurityMDExtensions() {
leg1Bids = new NativePriceSize[DEPTH_COUNT];
leg1Asks = new NativePriceSize[DEPTH_COUNT];
}
public static Book Depth(this SecurityMD security, IReadOnlyDictionary<uint, SecurityMD> securities, ref ulong time) {
var bids = new NativePriceSize[DEPTH_COUNT];
var asks = new NativePriceSize[DEPTH_COUNT];
var bidsOut = new (double, double, double)[(int)DEPTH_COUNT];
var asksOut = new (double, double, double)[(int)DEPTH_COUNT];
if (!security.security.HasSynthetic) {
security.md.get_depth(ref time, ref bids, ref asks, DEPTH_COUNT);
for (int i = 0; i < DEPTH_COUNT; i++) {
bidsOut[i] = (bids[i].price, bids[i].size, bids[i].notional_size);
asksOut[i] = (asks[i].price, asks[i].size, asks[i].notional_size);
}
return new(bidsOut, asksOut);
}
Log.assert(security.security.Synthetic.Legs.TryGetFirst(out var leg1));
if (!securities.TryGetValue(leg1.Security.Id, out var sec1)) {
return Book.Empty;
}
ulong sec1Time = 0;
if (!sec1.md.get_depth(ref sec1Time, ref leg1Bids, ref leg1Asks, DEPTH_COUNT)) {
return Book.Empty;
}
security.md.get_depth(ref time, ref bids, ref asks, DEPTH_COUNT);
int leg1BidsLevel = 0;
int leg1AsksLevel = 0;
double leg1BidsConsumed = 0;
double leg1AsksConsumed = 0;
double takeQuantity;
double notional;
for (int i = 0; i < DEPTH_COUNT && !bids[i].is_nan; i++) {
takeQuantity = bids[i].size * leg1.Quantity;
notional = getNotionalValue(ref leg1Bids, ref leg1BidsLevel, ref leg1BidsConsumed, ref takeQuantity);
bidsOut[i] = (bids[i].price, bids[i].size, notional);
takeQuantity = asks[i].size * leg1.Quantity;
notional = getNotionalValue(ref leg1Asks, ref leg1AsksLevel, ref leg1AsksConsumed, ref takeQuantity);
asksOut[i] = (asks[i].price, asks[i].size, notional);
}
return new(bidsOut, asksOut);
}
public static Book Depth(this SecurityMD security, NativePriceSize[] bids, NativePriceSize[] asks, IReadOnlyDictionary<uint, SecurityMD> securities) {
bids ??= new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
asks ??= new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
var bidsOut = new (double, double, double)[bids.Length];
var asksOut = new (double, double, double)[asks.Length];
var depthCount = (uint)Math.Min(bids.Length, asks.Length);
if (!security.security.HasSynthetic) {
for (int i = 0; i < depthCount; i++) {
bidsOut[i] = (bids[i].price, bids[i].size, bids[i].notional_size);
asksOut[i] = (asks[i].price, asks[i].size, asks[i].notional_size);
}
return new(bidsOut, asksOut);
}
Log.assert(security.security.Synthetic.Legs.TryGetFirst(out var leg1));
if (!securities.TryGetValue(leg1.Security.Id, out var sec1)) {
return Book.Empty;
}
ulong sec1Time = 0;
if (!sec1.md.get_depth(ref sec1Time, ref leg1Bids, ref leg1Asks, depthCount)) {
return Book.Empty;
}
int leg1BidsLevel = 0;
int leg1AsksLevel = 0;
double leg1BidsConsumed = 0;
double leg1AsksConsumed = 0;
double takeQuantity;
double notional;
for (int i = 0; i < depthCount && !bids[i].is_nan; i++) {
takeQuantity = bids[i].size;
notional = getNotionalValue(ref leg1Bids, ref leg1BidsLevel, ref leg1BidsConsumed, ref takeQuantity);
bidsOut[i] = (bids[i].price, bids[i].size, notional);
takeQuantity = asks[i].size;
notional = getNotionalValue(ref leg1Asks, ref leg1AsksLevel, ref leg1AsksConsumed, ref takeQuantity);
asksOut[i] = (asks[i].price, asks[i].size, notional);
}
return new(bidsOut, asksOut);
}
static double getNotionalValue(ref NativePriceSize[] depth, ref int level, ref double levelConsumed, ref double takeQuantity) {
if (level >= depth.Length) {
return double.NaN;
}
double notionalValue = 0;
while (takeQuantity > MathUtils.epsilon<double>() && level < depth.Length && !depth[level].is_nan) {
var lvlDepth = (double)depth[level].size - levelConsumed;
if (lvlDepth <= 0) {
level++;
levelConsumed = 0;
continue;
}
var lvlTake = Math.Min(takeQuantity, lvlDepth);
notionalValue += lvlTake * depth[level].price;
levelConsumed += lvlTake;
takeQuantity -= lvlDepth;
}
return notionalValue;
}
public class Book {
public Book(IEnumerable<(double Price, double Volume, double NotionalValue)> bids, IEnumerable<(double Price, double Quantity, double NotionalValue)> asks) {
Bids = bids;
Asks = asks;
}
public static Book Empty =>
new(Array.Empty<(double, double, double)>(), Array.Empty<(double, double, double)>());
public IEnumerable<(double Price, double Volume, double NotionalValue)> Bids { get; }
public IEnumerable<(double Price, double Volume, double NotionalValue)> Asks { get; }
}
}
}
@@ -48,6 +48,9 @@ namespace Spark.Api.Orders {
public static double quantity(this OrderRecordImmutable order) =>
order.HasState ? order.State.quantity() : 0;
public static double notional_value(this OrderRecordImmutable order) =>
order.quantity() * order.ui_price();
public static double working_quantity(this OrderRecordImmutable order) =>
order.HasState ? order.State.working_quantity() : 0;
@@ -487,5 +490,8 @@ namespace Spark.Api.Orders {
CustomOrderDefinition.CustomOneofCase.Composite => "COMP",
_ => algo_type.ToString(),
};
public static double notional_value(this FillImmutable fill) =>
fill.LastShares * fill.LastPrice;
}
}
@@ -10,58 +10,64 @@ using System.IO;
using System.Reactive.Disposables;
using mbts;
using System.Threading;
using System.Runtime.InteropServices;
namespace bts.api.core {
internal class QuickbusClientChannel : ClientChannel {
IntPtr _client;
internal class QuickbusClientChannel : ClientChannel {
IntPtr _client;
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
}
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
Log.QBus.diag($"Created QuickbusClientChannel");
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
}
public override void Dispose() {
ApiNativeMethods.api_client_destroy(_client);
_client = IntPtr.Zero;
}
public override void Dispose() {
Log.QBus.diag($"Disposed QuickbusClientChannel");
ApiNativeMethods.api_client_destroy(_client);
_client = IntPtr.Zero;
}
int _req_id = 0;
Dictionary<int, PBCallbackWrapper> _keepalive_cache = new();
unsafe public override Task<Result<Response>> send<Request, Response>(int method_id, Request request) {
var id = Interlocked.Increment(ref _req_id);
var header = new RequestHeader() {
Method = method_id,
RequestId = id,
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
};
var buffer = header.serialize(request);
var tcs = new TaskCompletionSource<Result<Response>>();
//ApiNativeMethods.api_client_log_error($"Call {id}");
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
//ApiNativeMethods.api_client_log_error($"Reply {id}");
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
if (res.header.Code == ApiError.Success) {
tcs.TrySetResult(new Result<Response>(res.body));
}
else {
tcs.TrySetResult(new Result<Response>(res.header.Code));
}
var id = Interlocked.Increment(ref _req_id);
var header = new RequestHeader() {
Method = method_id,
RequestId = id,
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
};
var buffer = header.serialize(request);
var tcs = new TaskCompletionSource<Result<Response>>();
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
if (res.header.Code == ApiError.Success) {
tcs.TrySetResult(new Result<Response>(res.body));
}
else {
tcs.TrySetResult(new Result<Response>(res.header.Code));
}
lock (_keepalive_cache) {
_keepalive_cache.Remove(id);
}
};
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
var cb_wrapper = new PBCallbackWrapper(callback);
lock (_keepalive_cache) {
_keepalive_cache.Remove(id);
_keepalive_cache.Add(id, cb_wrapper);
}
};
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
var cb_wrapper = new PBCallbackWrapper(callback);
lock (_keepalive_cache) {
_keepalive_cache.Add(id, cb_wrapper);
}
ApiNativeMethods.api_client_send(
_client, buffer, buffer.Length, cb_wrapper.callback, IntPtr.Zero);
return tcs.Task;
}
ApiNativeMethods.api_client_send(
_client, buffer, buffer.Length, cb_wrapper.Callback, IntPtr.Zero);
// Return a task that will free the handle once the task is completed
return tcs.Task.ContinueWith(task => {
cb_wrapper.Free(); // Ensure the GCHandle is freed when the callback is done
return task.Result;
});
}
public override IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(int method_id, string method, string qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter) {
var result = Observable.Create<RawUpdate<TOut>>(async observer => {
@@ -206,11 +212,29 @@ namespace bts.api.core {
/// garbage-collector race may yield the infamous error, "A callback was made on a garbage
/// collected delegate of type...").
/// </remarks>
class PBCallbackWrapper {
public readonly ApiNativeMethods.raw_pb_callback callback;
class PBCallbackWrapper
{
public readonly ApiNativeMethods.raw_pb_callback Callback;
private GCHandle _handle;
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback) =>
this.callback = callback;
}
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback)
{
// Pin the callback to prevent garbage collection
Log.QBus.diag($"Created PBCallbackWrapper");
_handle = GCHandle.Alloc(callback);
Callback = callback;
}
public void Free()
{
// Free the GCHandle when the delegate is no longer needed
Log.QBus.diag($"Deleting PBCallbackWrapper");
if (_handle.IsAllocated)
{
_handle.Free();
}
}
}
}
}
@@ -16,6 +16,9 @@ namespace mbts.user.settings {
public RegionalTickFactors()
: this(new TickFactors(), new TickFactors(), double.MinValue) { }
public RegionalTickFactors(TickFactors tick)
: this(tick, tick, double.PositiveInfinity) { }
public RegionalTickFactors(TickFactors low_tick, TickFactors high_tick, double low_tick_inclusive_end) {
_low_tick = low_tick;
_high_tick = high_tick;
@@ -78,14 +78,16 @@ namespace mbts.user.settings {
bool? show_commas = null,
string? nan_text = null,
bool? show_decimal_part = null,
bool? show_zero_integer = null) =>
bool? show_zero_integer = null,
TickFactors? tick_factors = null) =>
new() {
pad_with_leading_zeros = pad_with_leading_zeros ?? settings.pad_with_leading_zeros,
display_multiplier = display_multiplier ?? settings.display_multiplier,
show_commas = show_commas ?? settings.show_commas,
nan_text = nan_text ?? settings.nan_text,
show_decimal_part = show_decimal_part ?? settings.show_decimal_part,
show_zero_integer = show_zero_integer ?? settings.show_zero_integer
show_zero_integer = show_zero_integer ?? settings.show_zero_integer,
tick_factors = tick_factors ?? settings.tick_factors,
};
public static double to_display_value(this TickDisplaySettings settings, double value) =>
@@ -7,6 +7,7 @@ namespace mbts {
using mbts.user.settings;
using Spark.Core;
using Spark.Core.Math;
using System;
using System.Collections.Generic;
@@ -63,7 +64,7 @@ namespace mbts {
if (ticks_per_base.is_int()) {
return true;
}
return tick.tick_unit > tick.tick_base && tick.tick_base == 1;
return tick.tick_unit > tick.tick_base && tick.tick_unit_frac().is_int();
}
public static bool is_valid(this RegionalTickFactors regional_tick) =>
@@ -95,7 +96,7 @@ namespace mbts {
public static double reduced_base(this TickFactors tick) => MathUtils.factor_out(tick.tick_base, 10);
public static bool tick_equals(this RegionalTickFactors regional_tick, double x, double y) {
public static bool tick_equals(this RegionalTickFactors regional_tick, double x, double y, RoundingMode rounding_mode = RoundingMode.Closest) {
if (x.is_nan()) {
return y.is_nan();
}
@@ -107,15 +108,15 @@ namespace mbts {
return xtick is not null
&& ytick is not null
&& xtick.Equals(ytick)
&& xtick.tick_equals(x, y);
&& xtick.tick_equals(x, y, rounding_mode);
}
public static bool tick_equals(this TickFactors tick, double x, double y) {
public static bool tick_equals(this TickFactors tick, double x, double y, RoundingMode rounding_mode = RoundingMode.Closest) {
if (x.is_nan()) {
return y.is_nan();
}
var x_units = MathUtils.round_fractional_units(x, tick.tick_unit_frac());
var y_units = MathUtils.round_fractional_units(y, tick.tick_unit_frac());
var x_units = MathUtils.round_fractional_units(x, tick.tick_unit_frac(), rounding_mode, TickUtils.precision_const<double>());
var y_units = MathUtils.round_fractional_units(y, tick.tick_unit_frac(), rounding_mode, TickUtils.precision_const<double>());
return x_units == y_units;
}
@@ -137,10 +138,10 @@ namespace mbts {
x => NumericCastConverter<T, int>.convert(
MathUtils.round_fractional_units(x, tick.tick_unit_frac<T>())));
public static IEqualityComparer<double> tick_equality_comparer(this TickFactors tick) =>
public static IEqualityComparer<double> tick_equality_comparer(this TickFactors tick, RoundingMode rounding_mode = RoundingMode.Closest) =>
EqualityComparer.Create<double>(
(a, b) => tick_equals(tick, a, b),
x => (int)MathUtils.round_fractional_units(x, tick.tick_unit_frac()));
(a, b) => tick_equals(tick, a, b, rounding_mode),
x => (int)MathUtils.round_fractional_units(x, tick.tick_unit_frac(), rounding_mode));
public static TickFactors get_tick_for(this RegionalTickFactors rtick, double value) =>
value > rtick.low_tick_inclusive_end ? rtick.high_tick : rtick.low_tick;
@@ -179,10 +180,7 @@ namespace mbts {
if (T.IsInfinity(value)) {
return value.ToString();
}
return tick?.is_valid() ?? false ?
_format_tick(tick, value, settings) :
_format_no_tick(
value, settings.display_multiplier.as_type<double, T>(), settings.show_commas);
return _format_tick(tick, value, settings);
}
public static string format<T>(
+60 -93
View File
@@ -6,6 +6,7 @@ using bts.utils;
using mbts.user.settings;
using Spark.Core;
using Spark.Core.Math;
using System;
using System.Collections.Generic;
@@ -189,52 +190,58 @@ namespace mbts.spark {
public static int compare_tick_fallback_to_double(this ContractSpecificationImmutable contract, double x, double y) =>
tick_equals(contract, x, y) ? 0 : x.CompareTo(y);
public static double round_to_tick(this ContractSpecificationImmutable spec, double value) =>
round_to_tick(spec.ticksize(value), value);
public static double round_to_tick(this ContractSpecificationImmutable spec, double value, RoundingMode rounding_mode = RoundingMode.Closest) =>
round_to_tick(spec.ticksize(value), value, rounding_mode);
public static decimal round_to_tick(this ContractSpecificationImmutable spec, decimal value) =>
round_to_tick((decimal)spec.ticksize((double)value), value);
public static decimal round_to_tick(this ContractSpecificationImmutable spec, decimal value, RoundingMode rounding_mode = RoundingMode.Closest) =>
round_to_tick((decimal)spec.ticksize((double)value), value, rounding_mode);
public static double round_to_tick(this RegionalTickFactors rtick, double value) =>
round_to_tick(rtick.ticksize(value), value);
public static double round_to_tick(this RegionalTickFactors rtick, double value, RoundingMode rounding_mode = RoundingMode.Closest) =>
round_to_tick(rtick.ticksize(value), value, rounding_mode);
public static decimal round_to_tick(this RegionalTickFactors rtick, decimal value) =>
round_to_tick((decimal)rtick.ticksize((double)value), value);
public static decimal round_to_tick(this RegionalTickFactors rtick, decimal value, RoundingMode rounding_mode = RoundingMode.Closest) =>
round_to_tick((decimal)rtick.ticksize((double)value), value, rounding_mode);
public static double round_to_tick(double tick_size, double value) {
double intpart = (long)value;
double floatpart = value % 1;
floatpart = ((int)Math.Round(floatpart / tick_size)) * tick_size;
return intpart + floatpart;
public static double round_to_tick(double tick_size, double value, RoundingMode rounding_mode = RoundingMode.Closest) {
double n = rounding_mode switch {
RoundingMode.Closest => Math.Round(value / tick_size),
RoundingMode.Up => Math.Ceiling((value - _precision_const) / tick_size),
RoundingMode.Down => Math.Floor((value + _precision_const) / tick_size),
RoundingMode.AbsUp => value < 0
? Math.Floor((value + _precision_const) / tick_size)
: Math.Ceiling((value - _precision_const) / tick_size),
RoundingMode.AbsDown => value < 0
? Math.Ceiling((value - _precision_const) / tick_size)
: Math.Floor((value + _precision_const) / tick_size),
_ => double.NaN,
};
return n * tick_size;
}
public static decimal round_to_tick(decimal tick_size, decimal value) {
decimal intpart = (long)value;
decimal floatpart = value % 1;
floatpart = ((int)(floatpart / tick_size)) * tick_size;
return intpart + floatpart;
public static decimal round_to_tick(decimal tick_size, decimal value, RoundingMode rounding_mode = RoundingMode.Closest) {
decimal n = rounding_mode switch {
RoundingMode.Closest => Math.Round(value / tick_size),
RoundingMode.Up => Math.Ceiling(value / tick_size),
RoundingMode.Down => Math.Floor(value / tick_size),
RoundingMode.AbsUp => value < 0
? Math.Floor(value / tick_size)
: Math.Ceiling(value / tick_size),
RoundingMode.AbsDown => value < 0
? Math.Ceiling(value / tick_size)
: Math.Floor(value / tick_size),
_ => throw new ArgumentException("Invalid rounding mode", nameof(rounding_mode)),
};
return n * tick_size;
}
public static long? count_ticks(this RegionalTickFactors rtick, double value) {
if (!rtick.is_valid()) {
return null;
}
public static long? count_ticks(this RegionalTickFactors rtick, double value, RoundingMode rounding_mode = RoundingMode.AbsDown) {
var rounded_value = rtick.round_to_tick(value, rounding_mode);
var n = _count_ticks(
rtick.low_tick.tick_unit_frac(),
rtick.low_tick_inclusive_end,
rtick.high_tick.tick_unit_frac(),
0,
value);
var orig_n = n;
// Enforce proper rounding
var rounded_val = rtick.round_to_tick(value);
var half_tick = rtick.get_tick_for(value).tick_unit_frac() / 2;
if (n < 0 && (value - rounded_val) > half_tick) {
++n;
} else if (n > 0 && (rounded_val - value) > half_tick) {
--n;
}
Log.assert(n == orig_n);
rounded_value);
return n;
}
@@ -253,71 +260,19 @@ namespace mbts.spark {
return tick;
}
public static RegionalTickFactors multiply_tick_size(RegionalTickFactors spec, uint factor) {
factor = _fit_ticks_per_row_to_dollars(spec, factor) ?? 1;
return new(
public static RegionalTickFactors multiply_tick_size(RegionalTickFactors spec, uint factor) =>
new(
new(spec.low_tick.tick_base, spec.low_tick.tick_unit * factor),
new(spec.high_tick.tick_base, spec.high_tick.tick_unit * factor),
spec.low_tick_inclusive_end);
}
public static double normalize_price(RegionalTickFactors rtick, double price) {
if (!price.is_real()) {
var maybe_ticks = rtick.count_ticks(price);
if (!maybe_ticks.HasValue) {
return price;
}
var ticks = rtick.count_ticks(price);
if (!ticks.HasValue) {
return price;
}
var normalized = rtick.tick(0, ticks.Value);
var tick = price > rtick.low_tick_inclusive_end ? rtick.high_tick : rtick.low_tick;
// Diff should always be < 1/2 tick
Log.assert((normalized - price).abs() < tick.tick_unit_frac() * 0.5);
return normalized;
}
public static uint fit_ticks_per_row_to_dollars(SecurityImmutable sec, uint ticks_per_row) =>
_fit_ticks_per_row_to_dollars(sec, ticks_per_row) ?? 1;
public static uint fit_ticks_per_row_to_dollars(RegionalTickFactors rtick, uint ticks_per_row) =>
_fit_ticks_per_row_to_dollars(rtick, ticks_per_row) ?? 1;
static uint? _fit_ticks_per_row_to_dollars(SecurityImmutable sec, uint ticks_per_row) =>
_fit_ticks_per_row_to_dollars(sec.regional_ticks(), ticks_per_row);
static uint? _fit_ticks_per_row_to_dollars(RegionalTickFactors tf, uint ticks_per_row) {
if (ticks_per_row == 0)
return null;
// it is assumed that low tick will be the same as high tick;
// however, best to double check - return null if this isn't the case (let the caller decide what to do).
if (!tf.low_tick.Equals(tf.high_tick) || !tf.is_valid() || tf.low_tick.is_integer())
return null;
var ticks_per_dollar = (uint)(tf.low_tick.tick_base / tf.low_tick.tick_unit);
// if ticks_per_row is greater than ticks_per_dollar, round to a multiple of ticks_per_dollar
if (ticks_per_row > ticks_per_dollar) {
if (ticks_per_dollar == 0) {
return null;
}
return (uint)(ticks_per_dollar * Math.Floor((double)(ticks_per_row / ticks_per_dollar)));
} else if (ticks_per_row == ticks_per_dollar) {
return ticks_per_row;
} else {
uint max_decrease_adj = ticks_per_row;
uint max_increase_adj = ticks_per_dollar - ticks_per_row - 1;
for (uint adj = 0; adj < Math.Max(max_decrease_adj, max_increase_adj); adj++) {
if (adj < max_decrease_adj) {
var result = ticks_per_row - adj;
if (ticks_per_dollar % result == 0)
return result;
}
if (adj < max_increase_adj) {
var result = ticks_per_row + adj;
if (ticks_per_dollar % result == 0)
return result;
}
}
}
return null;
var ticks = maybe_ticks.Value;
return rtick.tick(0, (long)ticks);
}
static long? _count_ticks(
@@ -367,8 +322,20 @@ namespace mbts.spark {
static long count_single_tick_sz(
double tick_sz, double from, double to) {
var from_n = (long)MathUtils.round_fractional_units(from, tick_sz);
var to_n = (long)MathUtils.round_fractional_units(to, tick_sz);
from = round_to_tick(tick_sz, from, RoundingMode.AbsDown);
to = round_to_tick(tick_sz, to, RoundingMode.AbsDown);
if (from < 0) {
from -= _precision_const;
} else {
from += _precision_const;
}
if (to < 0) {
to -= _precision_const;
} else {
to += _precision_const;
}
var from_n = (long)(from / tick_sz);
var to_n = (long)(to / tick_sz);
return to_n - from_n;
}
}
@@ -39,7 +39,6 @@
static IEnumerable<ContractLegImmutable> _get_root_legs(
ContractLegImmutable leg, double root_quantity) {
_validate_root_quantity(root_quantity);
if (leg.HasSecurity) {
return _get_root_legs(leg.Security, leg.signed_quantity() * root_quantity);
}
@@ -54,10 +53,8 @@
return new[] { new ContractLegImmutable(mleg) };
}
static IEnumerable<ContractLegImmutable> _get_root_legs(
SecurityImmutable security, double root_quantity) {
_validate_root_quantity(root_quantity);
return security.ContractCase switch {
static IEnumerable<ContractLegImmutable> _get_root_legs(SecurityImmutable security, double root_quantity) =>
security.ContractCase switch {
Security.ContractOneofCase.Spread => security.Spread.Legs.SelectMany(l => _get_root_legs(l.Security, root_quantity * l.signed_quantity())).ToArray(),
Security.ContractOneofCase.Synthetic => security.Synthetic.Legs.SelectMany(l => _get_root_legs(l.Security, root_quantity * l.signed_quantity())).ToArray(),
_ => new[] {
@@ -69,13 +66,5 @@
})
},
};
}
static void _validate_root_quantity(double root_quantity) {
if (!root_quantity.is_real_non_zero()) {
throw new ArgumentException(
$"Expected real non-zero value (actually {root_quantity})", nameof(root_quantity));
}
}
}
}
@@ -640,6 +640,9 @@ namespace System.Collections.Generic {
public static List<T> ToListSorted<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> key_selector, IComparer<TKey> key_comparer) =>
enumerable.ToListSorted(key_comparer.wrap(key_selector));
public static List<T> ToListSorted<T>(this IEnumerable<T> enumerable) where T : IComparable<T> =>
enumerable.ToListSorted(Comparer<T>.Default);
public static List<T> ToListSorted<T>(this IEnumerable<T> enumerable, IComparer<T> comparer) {
var list = new List<T>(enumerable);
list.Sort(comparer);
@@ -73,7 +73,8 @@ namespace bts.collections {
public IEnumerable<TRow> all_rows => _tree.all_rows;
public int Count => _rows.Count;
public int total_row_count => _tree.total_row_count;
public IEnumerable<TRow> GetAllRows(bool excludeFiltered = false) =>
_tree.GetAllRows(excludeFiltered);
public IDisposable defer_refresh() =>
_refresh.defer();
@@ -61,6 +61,8 @@ namespace bts.collections {
IEnumerable<INode<TRow>> _root_nodes =>
_filter == null ? _tree.RootNodes : _cached_filtered ?? (_cached_filtered = _tree.RootNodes.filtered(_filter).sorted_hard(_comparer));
public IEnumerable<TRow> GetAllRows(bool excludeFiltered = false) =>
excludeFiltered ? all_rows : _tree;
// collapse
public void collapse_row(TKey key) => _toggle_is_expanded(key, false);
public void collapse_all() => _toggle_is_expanded(false);
@@ -0,0 +1,17 @@
#nullable enable
namespace Spark.Core.Math;
public enum RoundingMode {
None,
// Round to the closest integer (up if decimal part is >= 0.5, otherwise down)
Closest,
// Round up
Up,
// Round down
Down,
// Round positive numbers up, and negative numbers down
AbsUp,
// Round positive numbers down, and negative numbers up
AbsDown
}
+163 -143
View File
@@ -1,143 +1,163 @@
namespace System {
using bts;
using System.Diagnostics.CodeAnalysis;
public static class ConvertUtils {
public static object try_convert(Type type, object obj) {
try_convert(type, obj, out object result);
return result;
}
#nullable enable
public static bool try_convert<T>(
object obj,
[NotNullWhen(true)]out T? result,
LogLevel? log_level = null,
LogSubsystem log_subsystem = LogSubsystem.gen,
string? name = null) {
if (obj is T t) {
result = t;
return true;
}
if (try_convert(typeof(T), obj, out var result_obj)) {
result = (T)result_obj;
return true;
}
if (log_level.HasValue) {
name = name is null ? "obj" : $"'{name}'";
var msg = $"Expected {name} to be of type '{typeof(T)}', actually " +
$"'{obj?.GetType()?.ToString() ?? "null"}'.";
Log.log(new(log_subsystem, log_level.Value, msg));
}
result = default;
return false;
}
#nullable disable
public static bool try_convert(Type type, object obj, out object result) {
if (obj == null) {
result = get_default(type);
return false;
} else if (type.IsPrimitive && obj is IConvertible) {
result = Convert.ChangeType(obj, type);
return true;
} else if (type.IsAssignableFrom(obj.GetType())) {
result = obj;
return true;
} else if (type.IsEnum && obj is string) {
result = try_parse_enum(type, obj.ToString());
return true;
} else if (type.IsNullable()) {
if (try_convert(type.GetGenericArguments()[0], obj, out result)) {
return true;
}
result = null;
return false;
} else {
result = get_default(type);
return false;
}
}
public static T try_convert<T>(object obj) =>
(T)try_convert(typeof(T), obj);
public static T try_convert<T>(object obj, T default_value) =>
try_convert(obj, out T result) ? result : default_value;
public static bool try_convert<T>(object obj, out T result) {
bool rv = try_convert(typeof(T), obj, out object r_obj);
result = (T)r_obj;
return rv;
}
public static object get_default(Type type) =>
type.IsValueType ? Activator.CreateInstance(type) : null;
public static T ValueOrDefault<T>(T? nullable) where T : struct => nullable.HasValue ? nullable.Value : default;
public static bool is_compatible<T>(object obj) { return obj is T || (obj == null && default(T) == null); }
public static bool try_convert_args<T1, T2>(object[] args, out T1 arg1, out T2 arg2) {
if (args == null || args.Length != 2) {
arg1 = default;
arg2 = default;
return false;
}
bool rv = try_convert(args[0], out arg1);
rv |= try_convert(args[1], out arg2);
return rv;
}
public static bool try_convert_args<T1, T2, T3>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3) {
if (args == null || args.Length != 3) {
arg1 = default;
arg2 = default;
arg3 = default;
return false;
}
bool rv = try_convert(args[0], out arg1);
rv |= try_convert(args[1], out arg2);
rv |= try_convert(args[2], out arg3);
return rv;
}
public static bool try_convert_args<T1, T2, T3, T4>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4) {
if (args == null || args.Length != 4) {
arg1 = default;
arg2 = default;
arg3 = default;
arg4 = default;
return false;
}
bool rv = try_convert(args[0], out arg1);
rv |= try_convert(args[1], out arg2);
rv |= try_convert(args[2], out arg3);
rv |= try_convert(args[3], out arg4);
return rv;
}
public static object try_parse_enum(Type enum_type, string value, bool ignore_case = true) {
if (!enum_type.IsEnum) throw new ArgumentException("Must an enum type.", nameof(enum_type));
Array vals = Enum.GetValues(enum_type);
var str_comp = ignore_case ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
foreach (object v in vals) {
if (string.Equals(v.ToString(), value, str_comp))
return v;
}
return Activator.CreateInstance(enum_type);
}
public static bool update_value_type<T>(ref T field, T newval) where T : struct, IEquatable<T> {
bool rv = !field.Equals(newval);
field = newval;
return rv;
}
public static class CastDelegate<T, TResult> {
public static readonly Func<T, TResult> value =
v => v is TResult r ? r : throw new InvalidCastException($"Failed to cast element in source collection from type {typeof(T)} to {typeof(TResult)}.");
}
}
}
namespace System {
using bts;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
public static class ConvertUtils {
public static object try_convert(Type type, object obj) {
try_convert(type, obj, out object result);
return result;
}
#nullable enable
public static bool try_convert<T>(
object obj,
[NotNullWhen(true)]out T? result,
LogLevel? log_level = null,
LogSubsystem log_subsystem = LogSubsystem.gen,
string? name = null) {
if (obj is T t) {
result = t;
return true;
}
if (try_convert(typeof(T), obj, out var result_obj)) {
result = (T)result_obj;
return true;
}
if (log_level.HasValue) {
name = name is null ? "obj" : $"'{name}'";
var msg = $"Expected {name} to be of type '{typeof(T)}', actually " +
$"'{obj?.GetType()?.ToString() ?? "null"}'.";
Log.log(new(log_subsystem, log_level.Value, msg));
}
result = default;
return false;
}
#nullable disable
public static bool try_convert(Type type, object obj, out object result) {
if (obj == null) {
result = get_default(type);
return false;
} else if (type.IsPrimitive && obj is IConvertible) {
result = Convert.ChangeType(obj, type);
return true;
} else if (type.IsAssignableFrom(obj.GetType())) {
result = obj;
return true;
} else if (type.IsEnum && obj is string) {
result = try_parse_enum(type, obj.ToString());
return true;
} else if (type.IsNullable()) {
if (try_convert(type.GetGenericArguments()[0], obj, out result)) {
return true;
}
result = null;
return false;
} else {
result = get_default(type);
return false;
}
}
public static T try_convert<T>(object obj) =>
(T)try_convert(typeof(T), obj);
public static T try_convert<T>(object obj, T default_value) =>
try_convert(obj, out T result) ? result : default_value;
public static bool try_convert<T>(object obj, out T result) {
bool rv = try_convert(typeof(T), obj, out object r_obj);
result = (T)r_obj;
return rv;
}
public static object get_default(Type type) =>
type.IsValueType ? Activator.CreateInstance(type) : null;
public static T ValueOrDefault<T>(T? nullable) where T : struct => nullable.HasValue ? nullable.Value : default;
public static bool is_compatible<T>(object obj) { return obj is T || (obj == null && default(T) == null); }
public static bool try_convert_args<T1, T2>(object[] args, out T1 arg1, out T2 arg2) {
if (args == null || args.Length != 2) {
arg1 = default;
arg2 = default;
return false;
}
bool rv = try_convert(args[0], out arg1);
rv |= try_convert(args[1], out arg2);
return rv;
}
public static bool try_convert_args<T1, T2, T3>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3) {
if (args == null || args.Length != 3) {
arg1 = default;
arg2 = default;
arg3 = default;
return false;
}
bool rv = try_convert(args[0], out arg1);
rv |= try_convert(args[1], out arg2);
rv |= try_convert(args[2], out arg3);
return rv;
}
public static bool try_convert_args<T1, T2, T3, T4>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4) {
if (args == null || args.Length != 4) {
arg1 = default;
arg2 = default;
arg3 = default;
arg4 = default;
return false;
}
bool rv = try_convert(args[0], out arg1);
rv |= try_convert(args[1], out arg2);
rv |= try_convert(args[2], out arg3);
rv |= try_convert(args[3], out arg4);
return rv;
}
public static bool TryConvertArgs<T1, T2, T3, T4, T5>(IEnumerable<object> args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4, out T5 arg5) {
using var en = args.GetEnumerator();
bool success = true;
convertNext(en, ref success, out arg1);
convertNext(en, ref success, out arg2);
convertNext(en, ref success, out arg3);
convertNext(en, ref success, out arg4);
convertNext(en, ref success, out arg5);
return success;
}
public static object try_parse_enum(Type enum_type, string value, bool ignore_case = true) {
if (!enum_type.IsEnum) throw new ArgumentException("Must an enum type.", nameof(enum_type));
Array vals = Enum.GetValues(enum_type);
var str_comp = ignore_case ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
foreach (object v in vals) {
if (string.Equals(v.ToString(), value, str_comp))
return v;
}
return Activator.CreateInstance(enum_type);
}
public static bool update_value_type<T>(ref T field, T newval) where T : struct, IEquatable<T> {
bool rv = !field.Equals(newval);
field = newval;
return rv;
}
static void convertNext<T>(IEnumerator<object> enumerator, ref bool success, out T arg) {
if (!success || !enumerator.MoveNext()) {
arg = default;
success = false;
}
success = try_convert(enumerator.Current, out arg);
}
public static class CastDelegate<T, TResult> {
public static readonly Func<T, TResult> value =
v => v is TResult r ? r : throw new InvalidCastException($"Failed to cast element in source collection from type {typeof(T)} to {typeof(TResult)}.");
}
}
}
+23 -4
View File
@@ -1,5 +1,6 @@
namespace bts.utils {
using Spark.Core;
using Spark.Core.Math;
using System;
using System.Collections.Generic;
@@ -194,6 +195,22 @@ namespace bts.utils {
public static T round_fractional_units<T>(T value, T fractional_unit) where T : IFloatingPoint<T> =>
T.Round(value / fractional_unit);
public static T round_fractional_units<T>(T value, T fractional_unit, RoundingMode rounding_mode) where T : struct, IFloatingPoint<T> =>
round_fractional_units(value, fractional_unit, rounding_mode, T.Zero);
public static T round_fractional_units<T>(T value, T fractional_unit, RoundingMode rounding_mode, T epsilon) where T : IFloatingPoint<T> =>
rounding_mode switch {
RoundingMode.Up => T.Ceiling((value - epsilon) / fractional_unit),
RoundingMode.Down => T.Floor((value + epsilon) / fractional_unit),
RoundingMode.AbsUp => value < T.Zero
? T.Floor((value + epsilon) / fractional_unit)
: T.Ceiling((value - epsilon) / fractional_unit),
RoundingMode.AbsDown => value < T.Zero
? T.Ceiling((value - epsilon) / fractional_unit)
: T.Floor((value + epsilon) / fractional_unit),
_ => T.Round(value / fractional_unit),
};
public static double round_down_abs(double value) =>
round_down_abs(value, EPSILON);
@@ -473,10 +490,12 @@ namespace bts.utils {
}
static bool try_mk_floating_point_type([NotNullWhen(true)] out Type type) {
try {
type = typeof(IFloatingPoint<>).MakeGenericType(typeof(T));
return type.IsAssignableFrom(typeof(T));
} catch { }
if (typeof(T).GetInterfaces().Any(iface => iface.IsGenericType && iface.GetGenericTypeDefinition().Equals(typeof(IFloatingPoint<>)))) {
try {
type = typeof(IFloatingPoint<>).MakeGenericType(typeof(T));
return type.IsAssignableFrom(typeof(T));
} catch { }
}
type = null;
return false;
}
@@ -65,10 +65,10 @@ namespace bts.utils {
public static bool epsilon_equals<T>(this T x, T y, T epsilon) where T : IFloatingPoint<T> =>
MathUtils.epsilon_equals(x, y, epsilon);
public static bool marginal_equals<T>(T x, T y) where T : IFloatingPoint<T> =>
marginal_equals(x, y, MathUtils.epsilon<T>());
public static bool MarginalEquals<T>(this T x, T y) where T : IFloatingPoint<T> =>
MarginalEquals(x, y, MathUtils.epsilon<T>());
public static bool marginal_equals<T>(T x, T y, T margin) where T : IFloatingPoint<T> =>
public static bool MarginalEquals<T>(this T x, T y, T margin) where T : IFloatingPoint<T> =>
MathUtils.marginal_equals(x, y, margin);
public static bool is_within_range<T>(
+3 -31
View File
@@ -6,6 +6,8 @@ using mbts;
using mbts.spark;
using mbts.user.settings;
using Spark.Core.Math;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -159,7 +161,7 @@ namespace bts.tests.security {
var price_break = tick.low_tick_inclusive_end;
foreach (var p in prices) {
var expected_ticks_from_zero = TickTestHelper.count_ticks(tick, p);
var actual_ticks_from_zero = TickUtils.count_ticks(tick, p);
var actual_ticks_from_zero = TickUtils.count_ticks(tick, p, RoundingMode.Closest);
Assert.That(expected_ticks_from_zero == actual_ticks_from_zero,
$"Expected ticks from zero to be '{expected_ticks_from_zero}', actually '{actual_ticks_from_zero}' " +
$"(low/high/pricebreak: {tick.low_tick}/{tick.high_tick}/{price_break}, price: {p}).");
@@ -177,24 +179,6 @@ namespace bts.tests.security {
}
}
void _test_tick_increment(ContractSpecificationImmutable spec, double from, double to) {
if (!from.is_real() || !to.is_real())
return;
var (min, max) = MathUtils.get_outliers(from, to);
if (spec.PriceBreak.is_real()) {
if (min >= spec.PriceBreak) {
//_test_single_tick(spec.HighTick, min, max);
} else if (max <= spec.PriceBreak) {
_test_tick_increment(spec, spec.LowTick, min, max);
} else {
//if (round_ticks(spec.LowTick, min, out var ))
_test_tick_increment_across_break(spec, min, max);
}
} else {
_test_tick_increment(spec, spec.LowTick, min, max);
}
}
void _test_tick_increment(RegionalTickFactors tick, double from, double to) {
if (!from.is_real() || !to.is_real())
return;
@@ -260,18 +244,6 @@ namespace bts.tests.security {
Assert.That(MathUtils.marginal_equals(batch_tick_down_result, rounded_min, TickUtils.precision_const<double>()));
}
void _test_tick_increment(ContractSpecificationImmutable spec, double tick_size, double min, double max) {
// min round/partial tick
Assert.That(TickTestHelper.round_ticks(tick_size, min, out var rounded_min, out var rounded_min_ticks));
var min_partial_tick = TickTestHelper.check_partial_tick(spec, min, rounded_min, true);
// max round/partial tick
Assert.That(TickTestHelper.round_ticks(tick_size, max, out var rounded_max, out var rounded_max_ticks));
var max_partial_tick = TickTestHelper.check_partial_tick(spec, max, rounded_max, false);
var full_ticks = rounded_max_ticks - rounded_min_ticks;
Assert.That(MathUtils.marginal_equals(rounded_max, spec.tickup(min, full_ticks + min_partial_tick), TickUtils.precision_const<double>()));
Assert.That(MathUtils.marginal_equals(rounded_min, spec.tickup(max, -(full_ticks + max_partial_tick)), TickUtils.precision_const<double>()));
}
void _test_tick_increment(RegionalTickFactors tick, double tick_size, double min, double max) {
// min round/partial tick
Assert.That(TickTestHelper.round_ticks(tick_size, min, out var rounded_min, out var rounded_min_ticks));
@@ -13,6 +13,6 @@ message DatabentoMarketDataConfig {
double replay_sleep_timer_sec = 50;
string key = 60;
string dataset = 70;
bool filter_securities_one_year= 80;
uint32 filter_securities_upto_n_months= 80;
bool load_reference_data = 90;
}
@@ -13,4 +13,6 @@ option cc_enable_arenas = true;
message MarketDataReplayConfig {
bts.api.Venue venue = 10;
double replay_speed = 20;
// whether to replay books
bool publish_boostrap_books_only = 30;
}
@@ -774,8 +774,14 @@ bts::api::orderman::RoutingRecord AdminServiceUtils::get_routing_record(
out_result.set_is_success(true);
return rv;
}
case api::VENUE_BINANCE: {
rv.set_session("binance_1");
rv.set_account(account_ptr->name());
rv.set_account(user_ptr->name());
out_result.set_is_success(true);
return rv;
}
case api::VENUE_FTX: // TODO
case api::VENUE_BINANCE: // TODO
case api::VENUE_BINANCE_COIN_FUTURES:
case api::VENUE_BINANCE_US:
case api::Venue::VENUE_COINBASE:
+201 -174
View File
@@ -4,16 +4,11 @@
#include "lib/api/services/OrderService.server.pb.h"
#include "lib/config/config.h"
#include "lib/config/protos/toggles.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/utils/PBUtils.h"
#include <memory>
namespace bts {
namespace admin {
using namespace bts::api::admin;
using namespace bts::api::orderman;
class AdminServiceUtils {
public:
struct ExchangeSubAccountResolver {
@@ -33,7 +28,7 @@ class AdminServiceUtils {
return is_int_account ? entry.bts_account_name()
: entry.exchange_subaccount();
};
if(sec == nullptr)
if (sec == nullptr)
return nullptr;
for (const auto &u : exch_sub_accounts.accounts()) {
if (get_account(u) == account)
@@ -59,224 +54,247 @@ class AdminServiceUtils {
////////////////////////////////////////////
// finds the max id out of all the existing connections
// (todo: deleted connections)
static uint32_t find_max_connection_id(const AdminData &data);
static uint32_t find_max_connection_id(const api::admin::AdminData &data);
// finds a connection by name and returns a mutable pointer to it
static const ExchangeConnection *find_connection(
const AdminData &data, const std::string &connection_name);
static const api::admin::ExchangeConnection *find_connection(
const api::admin::AdminData &data, const std::string &connection_name);
// finds the presence of an exchange in a connection
static bool has_exchange(
const ExchangeConnection &conn, bts::api::Venue exchange);
const api::admin::ExchangeConnection &conn, api::Venue exchange);
// finds the default connections for an exchange
// in a given account
static const ExchangeConnection *find_default_account_connection(
const AdminData &data,
const BTSAccount &account,
bts::api::Venue exchange);
static const ExchangeConnection *resolve_cme_connection(
const AdminData &data,
const BTSAccount &account,
static const api::admin::ExchangeConnection *find_default_account_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
api::Venue exchange);
static const api::admin::ExchangeConnection *resolve_cme_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
static const ExchangeConnection *resolve_ice_connection(
const AdminData &data,
const BTSAccount &account,
static const api::admin::ExchangeConnection *resolve_ice_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
static const ExchangeConnection *resolve_sim_connection(
const AdminData &data,
const BTSAccount &account,
static const api::admin::ExchangeConnection *resolve_sim_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
static const ExchangeConnection *resolve_connection(
const AdminData &data,
const BTSAccount &account,
const bts::api::Security &sec);
static const api::admin::ExchangeConnection *resolve_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
////////////////////////////////////////////
// connection mutators /////////////////////
////////////////////////////////////////////
// adds a new connection (does not check for name uniqueness)
static ExchangeConnection *add_connection(
AdminData &data, const ExchangeConnection &new_connection);
static api::admin::ExchangeConnection *add_connection(
api::admin::AdminData &data,
const api::admin::ExchangeConnection &new_connection);
// reads the connections from config and fills in the
// admin data (with names only)
static void fill_connections_from_config(AdminData &admin_data);
static void fill_connections_from_config(api::admin::AdminData &admin_data);
////////////////////////////////////////////
// account accessors ///////////////////////
////////////////////////////////////////////
// finds the account from the name and returns
// a mutable pointer to the account
static BTSAccount *find_account(
AdminData &data,
static api::admin::BTSAccount *find_account(
api::admin::AdminData &data,
const std::string &acct_name,
bool include_deleted = false);
static std::map<std::string, uint32_t> get_clearing_account_map(
const AdminData &);
const api::admin::AdminData &);
// finds the account from a clearing account (and its venue)
static BTSAccount const *find_account(
const AdminData &data,
static api::admin::BTSAccount const *find_account(
const api::admin::AdminData &data,
const std::string &clearing_account,
bts::api::Venue);
api::Venue);
// same as above for const pointer
static const BTSAccount *find_account(
const AdminData &data,
static const api::admin::BTSAccount *find_account(
const api::admin::AdminData &data,
const std::string &acct_name,
bool include_deleted = false);
// finds the account from the id and returns
// a mutable pointer to the account
static BTSAccount *find_account(
AdminData &data, uint32_t id, bool include_deleted = false);
static api::admin::BTSAccount *find_account(
api::admin::AdminData &data, uint32_t id, bool include_deleted = false);
// like above but returns a const pointer
static const BTSAccount *find_account(
const AdminData &data, uint32_t id, bool include_deleted = false);
static const api::admin::BTSAccount *find_account(
const api::admin::AdminData &data,
uint32_t id,
bool include_deleted = false);
// this function is an indirection layer for account access
// so deletions are dealt in a more deliberate manner
static std::vector<const BTSAccount *> accounts(
const AdminData &data, bool include_deleted);
static std::vector<BTSAccount *> accounts_mutable(
AdminData &data, bool include_deleted);
static std::vector<const api::admin::BTSAccount *> accounts(
const api::admin::AdminData &data, bool include_deleted);
static std::vector<api::admin::BTSAccount *> accounts_mutable(
api::admin::AdminData &data, bool include_deleted);
// finds the max id amongts all the existing accounts
static uint32_t find_max_acct_id(const AdminData &data);
static uint32_t find_max_acct_id(const api::admin::AdminData &data);
// gets the account data for cme (and goes up the hierarchy
// if needed)
static const CMEAccountData *get_account_cme_data(
const BTSAccount &account, const AdminData &data);
static const ICEAccountData *get_account_ice_data(
const BTSAccount &account, const AdminData &data);
static const SIMAccountData *get_account_sim_data(
const BTSAccount &account, const AdminData &data);
static const api::admin::CMEAccountData *get_account_cme_data(
const api::admin::BTSAccount &account,
const api::admin::AdminData &data);
static const api::admin::ICEAccountData *get_account_ice_data(
const api::admin::BTSAccount &account,
const api::admin::AdminData &data);
static const api::admin::SIMAccountData *get_account_sim_data(
const api::admin::BTSAccount &account,
const api::admin::AdminData &data);
// returns true if an account has a connection
bool static account_has_connection(
const BTSAccount &account, const std::string &connection_name);
const api::admin::BTSAccount &account,
const std::string &connection_name);
// finds the external/clearing account associated
// with an account
static std::string find_account_exchange_account(
const AdminData &data,
const BTSAccount &account,
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
api::Venue exchange,
AdminServiceOperationResult &out_result);
api::admin::AdminServiceOperationResult &out_result);
// returns a map of the accounts vs their 'is_active'
static std::map<uint32_t, bool> accounts_map(const AdminData &admin_data);
static std::map<uint32_t, bool> accounts_map(
const api::admin::AdminData &admin_data);
// validates that the update to the account is valid
static void validate_account_update(
const AdminData &admin_data,
const BTSAccount &updated_account,
AdminServiceOperationResult &result);
const api::admin::AdminData &admin_data,
const api::admin::BTSAccount &updated_account,
api::admin::AdminServiceOperationResult &result);
// validates that the account exchange changes are valid
// (ex > 12 characters for accounts at CME)
static void validate_account_exchange_data(
const BTSAccount &account, AdminServiceOperationResult &out_result);
static RoutingRecord get_routing_record(
const AdminData &data,
const api::admin::BTSAccount &account,
api::admin::AdminServiceOperationResult &out_result);
static api::orderman::RoutingRecord get_routing_record(
const api::admin::AdminData &data,
const std::string &account_name,
const std::string &user_name,
const bts::api::Security &sec,
AdminServiceOperationResult &out_result);
static RoutingRecord get_routing_record(
const AdminData &data,
const api::Security &sec,
api::admin::AdminServiceOperationResult &out_result);
static api::orderman::RoutingRecord get_routing_record(
const api::admin::AdminData &data,
uint32_t account_id,
uint32_t user_id,
const bts::api::Security &sec,
AdminServiceOperationResult &out_result);
const api::Security &sec,
api::admin::AdminServiceOperationResult &out_result);
////////////////////////////////////////////
// account mutators ////////////////////////
////////////////////////////////////////////
static BTSAccount *create_account(
AdminData &data,
static api::admin::BTSAccount *create_account(
api::admin::AdminData &data,
uint32_t parent_account_id,
const std::string &account_name,
const std::string &description,
bool is_active,
AdminServiceCreateAccountResult &result);
api::admin::AdminServiceCreateAccountResult &result);
// writes over the account data completely
static void update_account(
AdminData &data,
const BTSAccount &updated_account,
AdminServiceOperationResult &result);
api::admin::AdminData &data,
const api::admin::BTSAccount &updated_account,
api::admin::AdminServiceOperationResult &result);
static void delete_account(
AdminData &admin_data,
const IDAPIArgument &id,
AdminServiceOperationResult &result);
api::admin::AdminData &admin_data,
const api::admin::IDAPIArgument &id,
api::admin::AdminServiceOperationResult &result);
static void set_account_exchange_data(
BTSAccount *account, const ExchangeAccountData &exchange_data);
api::admin::BTSAccount *account,
const api::admin::ExchangeAccountData &exchange_data);
// this is not a generally useful function but since
// connection are in config, this cleans up the account's
// connections and syncs it with the list in AdminData
// connections and syncs it with the list in api::admin::AdminData
static void sync_account_connections(
const AdminData &data, BTSAccount &account);
const api::admin::AdminData &data, api::admin::BTSAccount &account);
////////////////////////////////////////////
// group accessors /////////////////////////
////////////////////////////////////////////
// given an id, finds the mutable pointer to a group
static BTSUserGroup *find_group(AdminData &data, uint32_t id);
static api::admin::BTSUserGroup *find_group(
api::admin::AdminData &data, uint32_t id);
// given an id, finds the const pointer to a group
static const BTSUserGroup *const find_group(
const AdminData &data, uint32_t id);
static const api::admin::BTSUserGroup *const find_group(
const api::admin::AdminData &data, uint32_t id);
// given a name, finds the mutable pointer to a group
static BTSUserGroup *find_group(
AdminData &data, const std::string &group_name);
static api::admin::BTSUserGroup *find_group(
api::admin::AdminData &data, const std::string &group_name);
// finds the max id out of all the groups
static uint32_t find_max_group_id(const AdminData &data);
static uint32_t find_max_group_id(const api::admin::AdminData &data);
// travels up the group tree until it finds a populated cme data
static const CMEUserData *get_group_cme_data(
const BTSUserGroup &group, const AdminData &data);
static const ICEUserData *get_group_ice_data(
const BTSUserGroup &group, const AdminData &data);
static const SIMUserData *get_group_sim_data(
const BTSUserGroup &group, const AdminData &data);
static const UserAccountPermissions *const get_group_user_permissions(
const bts::api::admin::AdminData &admin_data, uint32_t group_id);
static const api::admin::CMEUserData *get_group_cme_data(
const api::admin::BTSUserGroup &group,
const api::admin::AdminData &data);
static const api::admin::ICEUserData *get_group_ice_data(
const api::admin::BTSUserGroup &group,
const api::admin::AdminData &data);
static const api::admin::SIMUserData *get_group_sim_data(
const api::admin::BTSUserGroup &group,
const api::admin::AdminData &data);
static const api::admin::UserAccountPermissions *const
get_group_user_permissions(
const api::admin::AdminData &admin_data, uint32_t group_id);
////////////////////////////////////////////
// group mutators/ /////////////////////////
////////////////////////////////////////////
static BTSUserGroup *create_group(
AdminData &data,
static api::admin::BTSUserGroup *create_group(
api::admin::AdminData &data,
uint32_t parent_group_id,
const std::string &group_name,
AdminServiceOperationResult &result);
api::admin::AdminServiceOperationResult &result);
////////////////////////////////////////////
// user accessors //////////////////////////
////////////////////////////////////////////
static BTSUser *find_user(
AdminData &data,
static api::admin::BTSUser *find_user(
api::admin::AdminData &data,
const std::string &user_name,
bool include_deleted = false);
static const BTSUser *find_user(
const AdminData &data,
static const api::admin::BTSUser *find_user(
const api::admin::AdminData &data,
const std::string &user_name,
bool include_deleted = false);
static BTSUser *find_user(
AdminData &data, uint32_t id, bool include_deleted = false);
static const BTSUser *const find_user(
const AdminData &data, uint32_t id, bool include_deleted = false);
static std::vector<const BTSUser *> users(
const AdminData &data, bool include_deleted);
static std::vector<BTSUser *> users_mutable(
AdminData &data, bool include_deleted);
static uint32_t find_max_user_id(const AdminData &data);
static const CMEUserData *get_user_cme_data(
const BTSUser &user, const AdminData &data);
static const ICEUserData *get_user_ice_data(
const BTSUser &user, const AdminData &data);
static const SIMUserData *get_user_sim_data(
const BTSUser &user, const AdminData &data);
static api::admin::BTSUser *find_user(
api::admin::AdminData &data, uint32_t id, bool include_deleted = false);
static const api::admin::BTSUser *const find_user(
const api::admin::AdminData &data,
uint32_t id,
bool include_deleted = false);
static std::vector<const api::admin::BTSUser *> users(
const api::admin::AdminData &data, bool include_deleted);
static std::vector<api::admin::BTSUser *> users_mutable(
api::admin::AdminData &data, bool include_deleted);
static uint32_t find_max_user_id(const api::admin::AdminData &data);
static const api::admin::CMEUserData *get_user_cme_data(
const api::admin::BTSUser &user, const api::admin::AdminData &data);
static const api::admin::ICEUserData *get_user_ice_data(
const api::admin::BTSUser &user, const api::admin::AdminData &data);
static const api::admin::SIMUserData *get_user_sim_data(
const api::admin::BTSUser &user, const api::admin::AdminData &data);
// finds the "exchange user" (ex: "operator_id" at CME or "authorized_trader"
// at ICE) for a user
static std::string find_user_exchange_id(
const AdminData &data,
const BTSUser &user,
const api::admin::AdminData &data,
const api::admin::BTSUser &user,
api::Venue exchange,
AdminServiceOperationResult &out_result);
api::admin::AdminServiceOperationResult &out_result);
// i. uses the exchange and exchange_user to reverse lookup the user(s)
// ii. uses the exchange, connection and exchange_account to reverse lookup
// the account(s) iii. finds the first account from ii that a user from i is
// authorized to trade
static std::string find_user_company_id(
const AdminData &data,
const BTSUser &user,
const api::admin::AdminData &data,
const api::admin::BTSUser &user,
api::Venue exchange,
AdminServiceOperationResult &out_result);
api::admin::AdminServiceOperationResult &out_result);
static std::pair<const BTSAccount *, const BTSUser *>
static std::pair<const api::admin::BTSAccount *, const api::admin::BTSUser *>
find_user_account_matches(
const AdminData &admin_data,
const api::admin::AdminData &admin_data,
const std::string &connection,
const std::string &exchange_account,
const std::string &exchange_user,
@@ -284,100 +302,108 @@ class AdminServiceUtils {
// for a given user finds the account permissions (if permissions are
// derived from the group, it traverses the group hierarcy to find the
// correct permissions.
static const UserAccountPermissions *get_user_permissions(
const AdminData &admin_data, uint32_t user_id);
static const api::admin::UserAccountPermissions *get_user_permissions(
const api::admin::AdminData &admin_data, uint32_t user_id);
// for a given user, fills in the associated_accounts ref arg
// with the accounts the user has permission to trade in
static void find_user_trading_accounts(
const AdminData &admin_data,
const api::admin::AdminData &admin_data,
uint32_t user_id,
std::vector<const BTSAccount *> &associated_accounts);
std::vector<const api::admin::BTSAccount *> &associated_accounts);
// for a given account, fills in the associated_users ref arg
// with the users that can trade into the account
static void find_users_for_trading_account(
const AdminData &admin_data,
const api::admin::AdminData &admin_data,
uint32_t account_id,
std::vector<const BTSUser *> &associated_users);
std::vector<const api::admin::BTSUser *> &associated_users);
// validates that changes in user data satisfy all the
// constraints
static void validate_user_update(
const AdminData &admin_data,
const BTSUser &updated_user,
AdminServiceOperationResult &result);
const api::admin::AdminData &admin_data,
const api::admin::BTSUser &updated_user,
api::admin::AdminServiceOperationResult &result);
// vaidates the exchange part of user data updates
static void validate_user_exchange_data(
const BTSUser &updated_user, AdminServiceOperationResult &out_result);
const api::admin::BTSUser &updated_user,
api::admin::AdminServiceOperationResult &out_result);
static void get_md_entitlement_report(
const AdminData &admin_data,
std::vector<MDEntitlementReportUnit> &entitlements);
const api::admin::AdminData &admin_data,
std::vector<api::admin::MDEntitlementReportUnit> &entitlements);
// Gets a reporting unit out of CMEMicData
static MDEntitlementReportUnit get_cme_md_entitlement_unit(
const BTSUser &user, const CMEMicData &cme_mic_data);
static MDEntitlementReportUnit get_ice_md_entitlement_unit(
const BTSUser &user, const ICEMicData &ice_mic_data);
static api::admin::MDEntitlementReportUnit get_cme_md_entitlement_unit(
const api::admin::BTSUser &user,
const api::admin::CMEMicData &cme_mic_data);
static api::admin::MDEntitlementReportUnit get_ice_md_entitlement_unit(
const api::admin::BTSUser &user,
const api::admin::ICEMicData &ice_mic_data);
static void get_user_symbol_permissions(
const AdminData &data,
const BTSUser &user,
std::vector<UserSymbolMDPermissionUnit> &res);
const api::admin::AdminData &data,
const api::admin::BTSUser &user,
std::vector<api::admin::UserSymbolMDPermissionUnit> &res);
////////////////////////////////////////////
// user mutators ///////////////////////////
////////////////////////////////////////////
static BTSUser *create_user(
AdminData &data,
static api::admin::BTSUser *create_user(
api::admin::AdminData &data,
uint32_t parent_group_id,
const std::string &name,
const std::string &password,
const std::string &full_name,
const std::string &company,
bool is_active,
const LicenseData &license_data,
const LicenseEntitlements &license_entitlements,
AdminServiceCreateEntityResult &result);
static BTSUser *update_user(
AdminData &admin_data,
const BTSUser &updated_user,
AdminServiceOperationResult &result);
const api::admin::LicenseData &license_data,
const api::admin::LicenseEntitlements &license_entitlements,
api::admin::AdminServiceCreateEntityResult &result);
static api::admin::BTSUser *update_user(
api::admin::AdminData &admin_data,
const api::admin::BTSUser &updated_user,
api::admin::AdminServiceOperationResult &result);
static void delete_user(
AdminData &admin_data,
const IDAPIArgument &id,
AdminServiceOperationResult &result);
api::admin::AdminData &admin_data,
const api::admin::IDAPIArgument &id,
api::admin::AdminServiceOperationResult &result);
static void set_user_exchange_data(
BTSUser *user, const ExchangeUserData &exchange_user_data);
static void clear_user_accounts(BTSUser *user);
api::admin::BTSUser *user,
const api::admin::ExchangeUserData &exchange_user_data);
static void clear_user_accounts(api::admin::BTSUser *user);
static void set_user_accounts(
BTSUser *user, const UserAccountPermissions &user_account_permissions);
api::admin::BTSUser *user,
const api::admin::UserAccountPermissions &user_account_permissions);
////////////////////////////////////////////
// risk limits //////////////////////////////
////////////////////////////////////////////
// sorting function used by sort_limits to
// sort SecurityScope's
static bool _sort_security_scopes(
const SecurityScope &a, const SecurityScope &b);
const api::admin::SecurityScope &a, const api::admin::SecurityScope &b);
// sorting function used by sort_limits to
// sort ProductScope's
static bool _sort_product_scopes(
const ProductScope &a, const ProductScope &);
const api::admin::ProductScope &a, const api::admin::ProductScope &);
// this sorts the rules such that the most
// specific ones are at the top. this makes it
// easier to match rules with securities/products
static void sort_limits(BTSAccount &account);
// creates a copy of AdminData with sorted rules
static AdminData sort_limits(const AdminData &admin_data);
static void sort_limits(api::admin::BTSAccount &account);
// creates a copy of api::admin::AdminData with sorted rules
static api::admin::AdminData sort_limits(
const api::admin::AdminData &admin_data);
// find the clip size for a security, account
static const uint32_t find_account_security_clip_size(
const BTSAccount &account, const api::Security &security);
const api::admin::BTSAccount &account, const api::Security &security);
// find the position limit for an account, security
static const AccountSecurityPositionLimitUnit *
static const api::admin::AccountSecurityPositionLimitUnit *
find_account_security_position_limit(
const BTSAccount &account, const api::Security &security);
const api::admin::BTSAccount &account, const api::Security &security);
// find the position limit for an account, product
static const AccountProductPositionLimitUnit *
static const api::admin::AccountProductPositionLimitUnit *
find_account_product_position_limit(
const BTSAccount &account, const api::Product &product);
const api::admin::BTSAccount &account, const api::Product &product);
static bool match_security_scope(
const SecurityScope &scope, const api::Security &security);
const api::admin::SecurityScope &scope, const api::Security &security);
static bool match_product_scope(
const ProductScope &scope, const api::Product &product);
const api::admin::ProductScope &scope, const api::Product &product);
static bool match_product(
const api::Security &security, const api::Product &product);
static bool _match_limit_rule_symbol(
@@ -388,13 +414,14 @@ class AdminServiceUtils {
////////////////////////////////////////////
static bool has_id(uint32_t id);
static bool is_symbol_set(const std::string &symbol);
static void bootstrap_data(bts::api::admin::AdminData &data);
static void bootstrap_data(api::admin::AdminData &data);
static void get_spread_prod_qty(
const api::Security &s,
std::map<std::string, double> &out_map,
double mult = 1.0);
// returns true if this spread effects the position of any outrights
static bool residual_pos_spread(const api::Security &s);
};
};
} // namespace admin
} // namespace bts
@@ -65,6 +65,7 @@ function(get_cpp_sources dest)
domain/DomainSecurityUtils.cpp
domain/SecurityCriteria.cpp
domain/SecurityCriteriaClause.cpp
domain/synthetic_book_side.cpp
domain_model/Security.cpp
domain_model/SecurityTests.cpp
domain_model/sod.cpp
@@ -64,6 +64,7 @@ function(get_cpp_sources dest)
domain/DomainSecurityUtils.cpp
domain/SecurityCriteria.cpp
domain/SecurityCriteriaClause.cpp
domain/synthetic_book_side.cpp
domain_model/Security.cpp
domain_model/SecurityTests.cpp
domain_model/sod.cpp
@@ -4026,12 +4026,34 @@ JetFIX {
connection: @JetFIXSimConnection.sim
connection {
name: "deribit_1"
max_order_msgs_per_window: 300
venue: VENUE_DERIBIT
}
connection {
name: "okx_1"
max_order_msgs_per_window: 300
venue: VENUE_OKX
}
connection {
name: "binance_usds_futures_1"
max_order_msgs_per_window: 300
venue: VENUE_BINANCE_USDS_FUTURES
}
connection {
name: "binance_1"
max_order_msgs_per_window: 300
venue: VENUE_BINANCE
}
connection {
name: "cboe_1"
max_order_msgs_per_window: 300
venue: VENUE_CBOE
}
connection {
name: "taifex_1"
max_order_msgs_per_window: 300
venue: VENUE_TAIFEX
}
# CME EXECUTION NEEDS TO BE UPDATED, probably below
# connection {
# name: "onixs_sim"
@@ -1,5 +1,6 @@
Spreader {
value {
n_failed_iocs: 20
qty_multiplier: 100000
}
}
@@ -37,6 +37,7 @@ SymbolLists {
list {
key: "CBOE"
symbol:"SPX"
symbol:"SPXW"
}
list {
key: "BINANCE_US"
-2
View File
@@ -39,8 +39,6 @@ LadderKnobs {
security_timeout_secs: 60
use_algo_param_grid: true
max_mbo_orders: 200
lighten_market_cells: true
mimic_tt: true
scrollable_tick_window_size: 256
}
}
+1
View File
@@ -4,6 +4,7 @@ package bts.config;
message Spreader {
uint32 n_failed_iocs = 10;
uint32 qty_multiplier = 15;
// if this is set to true, the spreader internally
// generates fake fills to keep the cache (data and
+4 -4
View File
@@ -82,7 +82,7 @@ message LadderKnobs {
bool show_scrollbar = 9;
// DJ wants bid/ask cells with markets (i.e., size > 0) to have a
// lighter background than those without markets.
bool lighten_market_cells = 10;
// bool lighten_market_cells = 10;
// For market cell backgrounds, 0 means 'ignore this color setting' (i.e.,
// employ the ladder's default behavior instead).
@@ -97,9 +97,9 @@ message LadderKnobs {
// Pure Red : 0xFFFF0000
// Pure Blue : 0xFF00FF00
// Pure Green : 0xFF0000FF
uint32 bid_market_cell_background = 11;
uint32 ask_market_cell_background = 12;
bool mimic_tt = 13;
// uint32 bid_market_cell_background = 11;
// uint32 ask_market_cell_background = 12;
// bool mimic_tt = 13;
// Defines the number of price rows rendered by the ladder. The smaller this
// window is, the cheaper it is to render the ladder grid (redraw prices,
// sizes, etc.). However, if the window is too small, scrolling may appear
+2 -2
View File
@@ -1,10 +1,10 @@
#pragma once
#include "BookLevel.h"
#include "BookLevelComp.h"
#include "BookSide.h"
#include "SecurityID.h"
#include "Side.h"
#include "lib/io/JsonReader.h"
#include <boost/container/small_vector.hpp>
#include <nlohmann/json.hpp>
+30
View File
@@ -33,6 +33,11 @@ class BookLevel {
void from_json(const nlohmann::json &j, BookLevel &bl);
void to_json(nlohmann::json &j, const BookLevel &bl);
constexpr bool operator==(const BookLevel &lhs, const BookLevel &rhs);
constexpr bool operator!=(const BookLevel &lhs, const BookLevel &rhs);
std::ostream &operator<<(std::ostream &out, const BookLevel &lvl) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
@@ -70,4 +75,29 @@ inline void to_json(nlohmann::json &j, const BookLevel &bl) {
};
}
constexpr bool operator==(const BookLevel &lhs, const BookLevel &rhs) {
return lhs.direct_quantity == rhs.direct_quantity &&
lhs.total_quantity == rhs.total_quantity &&
lhs.n_orders == rhs.n_orders &&
(lhs.price == rhs.price ||
(lhs.price.is_nan() && rhs.price.is_nan()));
}
constexpr bool operator!=(const BookLevel &lhs, const BookLevel &rhs) {
return !(lhs == rhs);
}
inline std::ostream &operator<<(
std::ostream &out, const BookLevel &lvl) noexcept {
out << lvl.price << " (" << lvl.direct_quantity;
if (lvl.direct_quantity != lvl.total_quantity) {
out << "/" << lvl.total_quantity;
}
if (lvl.n_orders != 0) {
out << ", " << lvl.n_orders;
}
out << ")";
return out;
}
} // namespace bts::domain
+31 -7
View File
@@ -1,18 +1,17 @@
#pragma once
#include "BookLevel.h"
#include "BookLevelComp.h"
#include "BookSide_PopulatedIterator.h"
#include "PriceComp.h"
#include "SecurityID.h"
#include "Side.h"
#include "lib/io/JsonReader.h"
#include <algorithm>
#include <boost/container/small_vector.hpp>
#include <initializer_list>
#include <iostream>
#include <iterator>
#include <limits>
#include <random>
#include <vector>
namespace bts::domain {
@@ -37,6 +36,8 @@ class BookSideT {
BookSideT &operator=(BookSideT &&) noexcept = default;
~BookSideT() = default;
constexpr BookLevel *data();
constexpr const BookLevel *data() const;
[[nodiscard]] constexpr BookLevel const &top() const;
constexpr BookLevel const &operator[](size_t i) const;
constexpr BookLevel &operator[](size_t i);
@@ -196,6 +197,16 @@ constexpr size_t BookSideT<SZ>::max_depth() noexcept {
return SZ;
}
template <std::size_t SZ>
constexpr BookLevel *BookSideT<SZ>::data() {
return _book.data();
}
template <std::size_t SZ>
constexpr const BookLevel *BookSideT<SZ>::data() const {
return _book.data();
}
template <std::size_t SZ>
constexpr const BookLevel &BookSideT<SZ>::top() const {
return _book[0];
@@ -234,12 +245,14 @@ BookSideT<SZ>::end() const noexcept {
}
template <std::size_t SZ>
constexpr typename BookSideT<SZ>::ReverseIterator BookSideT<SZ>::rbegin() noexcept {
constexpr typename BookSideT<SZ>::ReverseIterator
BookSideT<SZ>::rbegin() noexcept {
return _book.rbegin();
}
template <std::size_t SZ>
constexpr typename BookSideT<SZ>::ReverseIterator BookSideT<SZ>::rend() noexcept {
constexpr typename BookSideT<SZ>::ReverseIterator
BookSideT<SZ>::rend() noexcept {
return _book.rend();
}
@@ -250,12 +263,23 @@ constexpr size_t BookSideT<SZ>::size() const noexcept {
template <std::size_t SZ>
constexpr bool BookSideT<SZ>::operator==(BookSideT const &rhs) const {
return _book == rhs._book;
std::size_t i = 0;
for (; i < size(); i++) {
const BookLevel &l = (*this)[i];
if (i == rhs.size()) {
return l.is_empty();
}
const BookLevel &r = rhs[i];
if (l != r) {
return l.is_empty() && r.is_empty();
}
}
return i == rhs.size() || rhs[i].is_empty();
}
template <std::size_t SZ>
constexpr bool BookSideT<SZ>::operator!=(BookSideT const &rhs) const {
return _book != rhs._book;
return !(*this == rhs);
}
template <std::size_t SZ>
+77 -7
View File
@@ -1,16 +1,14 @@
#include "DomainSecurityUtils.h"
#include "FutureSecurity.h"
#include "OptionSecurity.h"
#include "SecurityCommon.h"
#include "SpotSecurity.h"
#include "SpreadSecurity.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/domain/TradeRecord.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/md/IntPrice.h"
#include "lib/md/PriceSize.h"
#include "lib/md/protos/co2.pb.h"
#include "lib/utils/bts_assert.h"
// #include "lib/domain/SecurityID.h"
// #include "lib/utils/overloaded.h"
// #include <fmt/format.h>
// #include <variant>
namespace bts::domain {
@@ -62,7 +60,7 @@ bts::api::Venue DomainSecurityUtils::convert(api::domain::Venue venue) {
case DOMAIN_VENUE(CBOE):
return api::Venue::VENUE_CBOE;
case DOMAIN_VENUE(TAIFEX):
return api::Venue::VENUE_TAIFEX;
return api::Venue::VENUE_TAIFEX;
case DOMAIN_VENUE(FTX):
return api::Venue::VENUE_FTX;
case DOMAIN_VENUE(BINANCE):
@@ -219,4 +217,76 @@ bool DomainSecurityUtils::get_security_desc(
return true;
}
domain::Price DomainSecurityUtils::convert(md::IntPrice iprice) {
int64_t mantissa = iprice.mantissa();
int8_t exp_diff = iprice.exponent() - domain::Price::exponent();
if (exp_diff != 0) {
mantissa *= std::pow(10, exp_diff);
}
return domain::Price{mantissa};
}
BookLevel DomainSecurityUtils::convert(md::PriceSize ps) {
BookLevel rv;
convert(ps, rv);
return rv;
}
void DomainSecurityUtils::convert(md::PriceSize ps, BookLevel &res) {
res.price = convert(ps.int_price());
res.total_quantity.value() = ps.size();
res.direct_quantity.value() = ps.direct_size();
res.n_orders = 0;
}
void DomainSecurityUtils::convert(
const bts::domain::BookSide &book_side,
bts::md::PriceSize *res,
size_t res_size) {
convert(book_side.data(), book_side.size(), res, res_size);
}
void DomainSecurityUtils::convert(
const BookLevel *book_side,
size_t size,
md::PriceSize *res,
size_t res_size) {
size_t sz = std::min(size, res_size);
size_t i = 0;
for (; i < sz; i++) {
// WARNING: Implied size is discarded here. There would be data loss anyways, since
// bts::md::PriceSize's implied size is a uint, and domain::BookLevel's is a float.
res[i] = md::PriceSize{
md::IntPrice{
book_side[i].price.mantissa(), book_side[i].price.exponent()},
book_side[i].total_quantity.value()};
}
// Set remaining levels to empty.
for (; i < res_size; i++) {
res[i] = bts::md::PriceSize{};
}
}
void DomainSecurityUtils::convert(
const md::PriceSize *book_side, std::size_t len, BookSide &res) {
if (res.size() < len) {
len = res.size();
}
while (len-- != 0) {
convert(book_side[len], res[len]);
}
}
void DomainSecurityUtils::convert(
const co2::TradeMessage &tm, TradeRecord &tr) {
tr.sec_id.value() = tm.key();
tr.time_stamp = core::TimeStamp::from_nanos(tm.time());
tr.agg_side = tm.aggressor_side() == co2::AggressorSide::AGGRESSOR_SIDE_BUY
? AggressorSide::Buy
: AggressorSide::Sell;
tr.trade_quantity.value() = tm.size();
tr.trade_price =
convert(md::IntPrice{tm.mantissa(), static_cast<int8_t>(tm.exponent())});
}
} // namespace bts::domain
+29 -4
View File
@@ -1,18 +1,28 @@
#pragma once
#include "FutureSecurity.h"
#include "OptionSecurity.h"
#include "SpotSecurity.h"
#include "SpreadSecurity.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/domain/SecurityID.h"
#include "lib/domain/BookSide.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/utils/overloaded.h"
#include <fmt/format.h>
#include <variant>
namespace bts::co2 {
class TradeMessage;
}
namespace bts::md {
class IntPrice;
class PriceSize;
} // namespace bts::md
namespace bts::domain {
class TradeRecord;
class DomainSecurityUtils {
public:
static api::domain::Venue convert(bts::api::Venue venue);
@@ -29,6 +39,21 @@ class DomainSecurityUtils {
static std::string get_exchange_id(const bts::api::Security &s);
static bool get_security_desc(
const bts::api::Security &security, std::string &desc);
static Price convert(md::IntPrice iprice);
static BookLevel convert(md::PriceSize ps);
static void convert(md::PriceSize ps, BookLevel &res);
static void convert(
const BookSide &book_side, md::PriceSize *res, std::size_t res_size);
static void convert(
const BookLevel *book_side,
std::size_t size,
md::PriceSize *res,
std::size_t res_size);
static void convert(
const md::PriceSize *book_side, std::size_t len, BookSide &res);
static void convert(const co2::TradeMessage &tm, TradeRecord &tr);
};
} // namespace bts::domain
+2 -2
View File
@@ -1,13 +1,13 @@
#pragma once
#include "AccountID.h"
#include "Price.h"
#include "Quantity.h"
#include "SecurityID.h"
#include "Side.h"
#include "TimeInForce.h"
#include "UserID.h"
#include "lib/domain/cme/GlbxOrderDefinition.h"
#include "lib/domain/deribit/DeribitOrderDefinition.h"
#include <string>
#include <variant>
+20 -21
View File
@@ -5,7 +5,6 @@
#include "lib/logging/logging.h"
#include "lib/utils/ValueWrapper.h"
#include <cstdint>
#include <nlohmann/json.hpp>
namespace bts::domain {
@@ -77,7 +76,7 @@ inline void to_json(nlohmann::json &j, const Quantity &sec_id) {
return lhs; \
}
#define QTY_UINT32_BIN_OP(result_t, op) \
#define QTY_VALUE_BIN_OP(result_t, op) \
constexpr result_t operator op(Quantity lhs, Quantity::Value rhs) { \
return result_t(lhs.value() op rhs); /*NOLINT*/ \
} \
@@ -85,7 +84,7 @@ inline void to_json(nlohmann::json &j, const Quantity &sec_id) {
return result_t(lhs op rhs.value()); \
}
#define QTY_UINT32_BIN_REF_OP(op) \
#define QTY_VALUE_BIN_REF_OP(op) \
inline Quantity &operator op(Quantity &lhs, Quantity::Value rhs) { \
lhs.value() op rhs; \
return lhs; \
@@ -107,29 +106,29 @@ QTY_QTY_BIN_REF_OP(*=)
QTY_QTY_BIN_REF_OP(/=)
// QTY_QTY_BIN_REF_OP(%=)
QTY_UINT32_BIN_OP(bool, !=)
QTY_UINT32_BIN_OP(bool, ==)
QTY_UINT32_BIN_OP(bool, <)
QTY_UINT32_BIN_OP(bool, <=)
QTY_UINT32_BIN_OP(bool, >)
QTY_UINT32_BIN_OP(bool, >=)
QTY_VALUE_BIN_OP(bool, !=)
QTY_VALUE_BIN_OP(bool, ==)
QTY_VALUE_BIN_OP(bool, <)
QTY_VALUE_BIN_OP(bool, <=)
QTY_VALUE_BIN_OP(bool, >)
QTY_VALUE_BIN_OP(bool, >=)
QTY_UINT32_BIN_OP(Quantity, +)
QTY_UINT32_BIN_OP(Quantity, -)
QTY_UINT32_BIN_OP(Quantity, *)
QTY_UINT32_BIN_OP(Quantity, /)
// QTY_UINT32_BIN_OP(Quantity, %)
QTY_VALUE_BIN_OP(Quantity, +)
QTY_VALUE_BIN_OP(Quantity, -)
QTY_VALUE_BIN_OP(Quantity, *)
QTY_VALUE_BIN_OP(Quantity, /)
// QTY_VALUE_BIN_OP(Quantity, %)
QTY_UINT32_BIN_REF_OP(+=)
QTY_UINT32_BIN_REF_OP(-=)
QTY_UINT32_BIN_REF_OP(*=)
QTY_UINT32_BIN_REF_OP(/=)
// QTY_UINT32_BIN_REF_OP(%=)
QTY_VALUE_BIN_REF_OP(+=)
QTY_VALUE_BIN_REF_OP(-=)
QTY_VALUE_BIN_REF_OP(*=)
QTY_VALUE_BIN_REF_OP(/=)
// QTY_VALUE_BIN_REF_OP(%=)
#undef QTY_QTY_BIN_OP
#undef QTY_QTY_BIN_REF_OP
#undef QTY_UINT32_BIN_OP
#undef QTY_UINT32_BIN_REF_OP
#undef QTY_VALUE_BIN_OP
#undef QTY_VALUE_BIN_REF_OP
} // namespace bts::domain
-1
View File
@@ -1,6 +1,5 @@
#include "Security.h"
#include "DomainSecurityUtils.h"
#include "lib/core/TableFormatter.h"
#include "lib/domain/FutureSecurity.h"
#include "lib/domain/OptionSecurity.h"
#include "lib/domain/SpotSecurity.h"
+3 -13
View File
@@ -1,22 +1,12 @@
#pragma once
#include "DomainSecurityUtils.h"
#include "Price.h"
#include "SecurityID.h"
#include "lib/api/domain/CallPut.pb.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/core/TimeStamp.h"
#include "lib/domain/FutureSecurity.h"
#include "lib/domain/LegDefinition.h"
#include "lib/domain/OptionSecurity.h"
#include "lib/domain/Quantity.h"
#include "lib/domain/Side.h"
#include "lib/domain/SpreadSecurity.h"
#include "lib/domain/DomainSecurityUtils.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/bts_assert.h"
#include <limits>
#include <nlohmann/json.hpp>
#include <string>
@@ -40,7 +30,7 @@ class SecurityCommon {
double min_quantity_increment = 0;
double min_quantity = 0;
double max_quantity = std::numeric_limits<double>::max();
double cash_per_point =1;
double cash_per_point = 1;
SecurityCommon() {}
SecurityCommon(const bts::api::Security &s)
+1 -4
View File
@@ -21,9 +21,6 @@ using std::regex_search;
using std::smatch;
using std::string;
#define CONTAINS(container, value) \
(std::find(container.begin(), container.end(), value) != container.end())
bool SecurityCriteriaClause::is_defined() const {
return !venues.empty() || !types.empty() || !assets.empty() ||
!groups.empty() || !ids.empty() || !names.empty() ||
@@ -104,7 +101,7 @@ SecurityCriteriaClause SecurityCriteriaClause::from_proto(
// be cheap). These limits are somewhat arbitrary. Adjust them as needed.
static const int MAX_VENUES_COUNT = 16;
static const int MAX_TYPES_COUNT = 8;
static const int MAX_SYMBOLS_COUNT = 32;
static const int MAX_SYMBOLS_COUNT = 1024;
static const int MAX_NAMES_COUNT = 64;
static const int MAX_NAME_REGEXPS_COUNT = 16;
bts_assert(api_clause.venues().size() <= MAX_VENUES_COUNT);
@@ -1,11 +1,13 @@
#pragma once
#include "Price.h"
#include "Quantity.h"
#include "Security.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain/SecurityID.h"
#include "lib/io/JsonReader.h"
#include "lib/md/IntPrice.h"
#include "lib/md/protos/co2.pb.h"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <ostream>
+5
View File
@@ -1,4 +1,7 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include <ostream>
namespace bts::domain {
@@ -153,3 +156,5 @@ constexpr Side operator*(Side lhs, Side::Value rhs) noexcept {
}
} // namespace bts::domain
FMT_IMPL(bts::domain::Side, s, "{}", s.name());
+2 -14
View File
@@ -1,12 +1,12 @@
#pragma once
#include "AggressorSide.h"
#include "Price.h"
#include "Quantity.h"
#include "SecurityID.h"
#include "lib/core/TimeStamp.h"
#include "lib/logging/logging.h"
#include "lib/md/IntPrice.h"
#include "lib/md/protos/co2.pb.h"
#include <nlohmann/json.hpp>
namespace bts::domain {
@@ -18,8 +18,6 @@ class TradeRecord {
AggressorSide agg_side = {};
Quantity trade_quantity;
Price trade_price;
TradeRecord() {}
inline TradeRecord(const bts::co2::TradeMessage &tm);
};
void from_json(const nlohmann::json &j, TradeRecord &tr);
@@ -29,16 +27,6 @@ void to_json(nlohmann::json &j, const TradeRecord &tr);
// Definition
//////////////////////////////////////////////////
inline TradeRecord::TradeRecord(const bts::co2::TradeMessage &tm)
: sec_id(tm.key()), time_stamp(tm.time()),
agg_side(
tm.aggressor_side() == co2::AggressorSide::AGGRESSOR_SIDE_BUY
? AggressorSide{AggressorSide::Buy}
: AggressorSide{AggressorSide::Sell}),
trade_quantity(tm.size()),
trade_price(Price::from_double(
bts::md::IntPrice(tm.mantissa(), tm.exponent()).price())) {}
inline void from_json(const nlohmann::json &j, TradeRecord &tr) {
io::JsonReader jr{&j};
// clang-format off
+107
View File
@@ -0,0 +1,107 @@
#pragma once
#include "lib/domain/BookSide.h"
#include "lib/domain/Quantity.h"
namespace bts::domain {
class BookSideConsumer {
// Use pointer so copying is cheap
const BookLevel *levels_;
std::size_t size_;
std::size_t curr_{0};
Quantity curr_consumed_{0};
public:
BookSideConsumer() = default;
BookSideConsumer(const BookLevel *levels, std::size_t size)
: levels_(levels), size_(size) {}
template <std::size_t SZ>
BookSideConsumer(
const BookSideT<SZ> *levels,
std::size_t curr = 0,
Quantity curr_consumed = Quantity{0});
BookSideConsumer(const BookSideConsumer &other) = default;
BookSideConsumer &operator=(BookSideConsumer &&other) = default;
BookSideConsumer &operator=(const BookSideConsumer &other) = default;
const BookLevel &operator[](std::size_t idx) const;
const BookLevel *levels() const { return levels_; }
size_t size() const;
bool is_done() const;
void reset();
Quantity available(std::size_t level_limit = 1) const;
std::pair<Quantity, Price> consume(Quantity quantity);
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
template <std::size_t SZ>
inline BookSideConsumer::BookSideConsumer(
const BookSideT<SZ> *levels, std::size_t curr, Quantity curr_consumed)
: levels_(levels), curr_(curr), curr_consumed_(curr_consumed) {}
inline const BookLevel &BookSideConsumer::operator[](std::size_t idx) const {
return levels_[idx];
}
inline size_t BookSideConsumer::size() const { return size_; }
inline bool BookSideConsumer::is_done() const {
if (curr_ >= size_) {
return true;
}
if (curr_ == size_ - 1) {
return curr_consumed_ >= levels_[curr_].total_quantity;
}
return curr_consumed_ >= levels_[curr_].total_quantity &&
levels_[curr_ + 1].total_quantity == 0;
}
inline void BookSideConsumer::reset() {
curr_ = 0;
curr_consumed_.value() = 0;
}
inline Quantity BookSideConsumer::available(std::size_t level_limit) const {
std::size_t end = size_ > curr_ + level_limit ? curr_ + level_limit : size_;
Quantity rv{0};
for (std::size_t i = curr_; i < end; i++) {
Quantity level_qty = levels_[i].total_quantity;
if (i == curr_) {
level_qty -= curr_consumed_;
}
rv += level_qty;
}
return rv;
}
inline std::pair<Quantity, Price> BookSideConsumer::consume(Quantity quantity) {
Quantity consumed{0};
Price price = Price::zero();
for (; curr_ < size_; curr_++) {
Quantity level_qty = levels_[curr_].total_quantity - curr_consumed_;
if (level_qty == 0) {
break;
}
Quantity rem = quantity - consumed;
if (level_qty > rem) {
curr_consumed_ += rem;
consumed = quantity;
price += levels_[curr_].price * rem.value();
break;
}
curr_consumed_.value() = 0;
consumed += level_qty;
price += levels_[curr_].price * level_qty.value();
}
return {consumed, price};
}
} // namespace bts::domain
+116
View File
@@ -0,0 +1,116 @@
#include "synthetic_book_side.h"
#include "lib/domain/book_side_consumer.h"
namespace bts::domain {
void SyntheticBookSide::copy_(const SyntheticBookSide &other) noexcept {
if (this == &other) {
return;
}
leg_qty_.value() = other.leg_qty_.value();
// Copy 'data_'
if (other.data_ != nullptr) {
// Clear 'own_data_'
if (own_data_ != nullptr) {
delete[] own_data_;
own_data_ = nullptr;
}
size_ = other.size_;
data_ = other.data_;
return;
}
data_ = nullptr;
if (other.size_ == 0) {
// Clear 'own_data_'
if (own_data_ != nullptr) {
delete[] own_data_;
own_data_ = nullptr;
}
size_ = 0;
return;
}
// Copy 'own_data_'
if (own_data_ == nullptr) {
own_data_ = new BookLevel[other.size_];
} else if (size_ != other.size_) {
delete[] own_data_;
own_data_ = new BookLevel[other.size_];
}
size_ = other.size_;
std::memcpy(own_data_, other.own_data_, size_ * sizeof(BookLevel));
}
void SyntheticBookSide::move_(SyntheticBookSide &&other) noexcept {
if (own_data_ != nullptr) {
delete[] own_data_;
}
leg_qty_.value() = other.leg_qty_.value();
data_ = other.data_;
size_ = other.size_;
own_data_ = other.own_data_;
other.leg_qty_.value() = 0;
other.data_ = nullptr;
other.own_data_ = nullptr;
other.size_ = 0;
}
SyntheticBookSide SyntheticBookSide::add_leg(
SyntheticBookSide other_leg,
BookLevel *synth_bk,
size_t synth_bk_size) const noexcept {
if (leg_qty_.value() == 0) {
return other_leg;
}
if (other_leg.leg_qty_.value() == 0) {
return *this;
}
SyntheticBookSide rv;
rv.leg_qty_.value() = 1;
rv.size_ = synth_bk_size;
if (synth_bk == nullptr) {
rv.own_data_ = synth_bk = new BookLevel[synth_bk_size];
} else {
rv.data_ = synth_bk;
}
BookSideConsumer l1{data(), size_};
BookSideConsumer l2{other_leg.data(), other_leg.size_};
Quantity l1_qty = leg_qty_;
Quantity l2_qty = other_leg.leg_qty_;
std::size_t i = 0;
for (; !l1.is_done() && !l2.is_done() && i < synth_bk_size; i++) {
Quantity leg2_per_leg1 = (l2_qty / l1_qty);
if (leg2_per_leg1 < 0) {
leg2_per_leg1.value() = -leg2_per_leg1.value();
}
Quantity leg1_per_leg2 = (l1_qty / l2_qty);
if (leg1_per_leg2 < 0) {
leg1_per_leg2.value() = -leg1_per_leg2.value();
}
Quantity avail1 = l1.available();
Quantity avail2 = l2.available();
Quantity take1;
Quantity take2;
if ((avail1 / leg1_per_leg2) < (avail2 / leg2_per_leg1)) {
take1 = avail1;
take2 = avail1 * leg2_per_leg1;
} else {
take1 = avail2 * leg1_per_leg2;
take2 = avail2;
}
const auto [qty1, cost1] = l1.consume(take1);
const auto [qty2, cost2] = l2.consume(take2);
Price price1 = (cost1 / qty1.value()) * l1_qty.value();
Price price2 = (cost2 / qty2.value()) * l2_qty.value();
auto &synth_lvl = synth_bk[i];
synth_lvl.price = price1 + price2;
synth_lvl.total_quantity = qty1;
synth_lvl.direct_quantity = qty1;
synth_lvl.n_orders = 0;
}
for (; i < synth_bk_size; i++) {
synth_bk[i] = BookLevel::empty();
}
return rv;
}
} // namespace bts::domain
+112
View File
@@ -0,0 +1,112 @@
#pragma once
#include "lib/domain/BookLevel.h"
#include "lib/domain/Quantity.h"
#include <cstddef>
#include <cstring>
namespace bts::domain {
class SyntheticBookSide {
Quantity leg_qty_{0};
// Either 'data_' or 'own_data_' can be non-null (not both). Only the latter
// is deleted in the destructor.
const BookLevel *data_{nullptr};
BookLevel *own_data_{nullptr};
std::size_t size_{0};
void copy_(const SyntheticBookSide &other) noexcept;
void move_(SyntheticBookSide &&other) noexcept;
public:
constexpr SyntheticBookSide() = default;
constexpr SyntheticBookSide(
Quantity leg_qty, const BookLevel *book_side, std::size_t size);
constexpr ~SyntheticBookSide();
SyntheticBookSide(const SyntheticBookSide &other);
SyntheticBookSide(SyntheticBookSide &&other);
SyntheticBookSide &operator=(const SyntheticBookSide &other);
SyntheticBookSide &operator=(SyntheticBookSide &&other);
constexpr std::size_t size() const;
constexpr const BookLevel *data() const;
/**
* Adds a leg to this one (calls can be chained to get the depth for N legs).
*
* @return SyntheticBookSide whose levels constitute the synthetic depth of
* this and the specified @p leg. Its quantity is always 1.
*/
SyntheticBookSide add_leg(SyntheticBookSide leg) const noexcept;
SyntheticBookSide add_leg(
SyntheticBookSide other_leg,
BookLevel *synth_bk,
size_t synth_bk_size) const noexcept;
/**
* Syntactic sugar for `add_leg`
*/
SyntheticBookSide operator+(SyntheticBookSide other) const noexcept;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
constexpr SyntheticBookSide::SyntheticBookSide(
Quantity leg_qty, const BookLevel *data, std::size_t size)
: leg_qty_(leg_qty), data_(data), own_data_(nullptr), size_(size) {}
constexpr SyntheticBookSide::~SyntheticBookSide() {
leg_qty_.value() = 0;
data_ = nullptr;
if (own_data_ != nullptr) {
delete[] own_data_;
own_data_ = nullptr;
}
size_ = 0;
}
inline SyntheticBookSide::SyntheticBookSide(const SyntheticBookSide &other) {
copy_(other);
}
inline SyntheticBookSide::SyntheticBookSide(SyntheticBookSide &&other) {
move_(std::move(other));
}
inline SyntheticBookSide &SyntheticBookSide::operator=(
const SyntheticBookSide &other) {
if (this != &other) {
copy_(other);
}
return *this;
}
inline SyntheticBookSide &SyntheticBookSide::operator=(
SyntheticBookSide &&other) {
if (this != &other) {
move_(std::move(other));
}
return *this;
}
constexpr std::size_t SyntheticBookSide::size() const { return size_; }
constexpr const BookLevel *SyntheticBookSide::data() const {
return data_ == nullptr ? own_data_ : data_;
}
inline SyntheticBookSide SyntheticBookSide::add_leg(
SyntheticBookSide other_leg) const noexcept {
return add_leg(other_leg, nullptr, 10);
}
inline SyntheticBookSide SyntheticBookSide ::operator+(
SyntheticBookSide other) const noexcept {
return add_leg(other);
}
} // namespace bts::domain
+1 -4
View File
@@ -1,10 +1,8 @@
#include "Security.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/config/config.h"
#include "lib/config/protos/orders_fills_cleanup.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/md/common.h"
#include "lib/utils/datetime.h"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <google/protobuf/text_format.h>
@@ -13,7 +11,6 @@ namespace bts {
namespace securities {
using bts::api::Security;
using bts::api::Venue;
using std::optional;
api::SecurityType security_type(api::Security const &security) {
-2
View File
@@ -7,10 +7,8 @@
#include "lib/domain_model/Venue.h"
#include "lib/domain_model/common.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/datetime.h"
#include <cmath>
#include <memory>
namespace boost {
namespace gregorian {
Loaded 100 of 262 files, more files were not shown because too many files have changed in this diff. Show more