Compare commits

...
Author SHA1 Message Date
John Colvin eadd6a0706 fix: Update title in new ladder ticket when sec received 2026-08-14 14:50:31 -04:00
John Colvin 503a5e2715 fix: Revert ladder to use fixed price list instead of virtual rows 2026-08-14 14:42:12 -04:00
John Colvin 786736b38f Add Orders column placeholder to new ladder grid 2026-08-14 14:42:12 -04:00
John Colvin 1ef2ed9d57 Make new ladder grid use 1 row per tick with fixed row count 2026-08-14 14:42:12 -04:00
John Colvin b39ea9528c Use fixed price rows (per tick) in new ladder 2026-08-14 14:42:12 -04:00
John Colvin c59ce773a7 Set security in OnSecurityMd callback in new ladder 2026-08-07 17:20:14 -04:00
arham 400981738f fix: bug fix now sell works 2026-08-05 15:47:21 -04:00
arham 3fd749dcd9 add: change colors and add ladder link 2026-08-05 14:04:32 -04:00
John Colvin a250967286 feat: Launch modify ticket w/ ladder 2026-07-30 14:13:35 -04:00
John Colvin a81126d888 feat: Launch ticket w/ ladder popup from mview 2026-07-30 14:13:35 -04:00
John Colvin c38ff5926e feat: Add new order ticket with ladder price levels 2026-07-30 14:13:35 -04:00
John Colvin 49df45574f Rename get_ladder_profile PascalCase 2026-07-30 11:51:37 -04:00
John Colvin 7b2e19f7c8 Remove unused ext fn subscribe_ladder_profile 2026-07-30 11:51:37 -04:00
John Colvin c9e3cf8c67 Rename AuxiliaryDataResources -> OrderAuxiliaryPopupResources
The old name was unnecessarily vague
2026-07-30 11:48:00 -04:00
John Colvin 6b31f7383c Remove unused code ConstrainedTextBlock 2026-07-30 11:48:00 -04:00
arham f2c95d6c28 sec filter on the basis of expiration in mdp3 2026-07-27 09:32:10 +00:00
Taha 1d3bbcc75f TC2 ATM highlighting wokrs now without AW2 setup 2026-07-22 16:52:49 -04:00
arham 94bfdacf8b add: trade latency logs 2026-07-02 16:21:08 +00:00
arham 2a8d407f08 conf: add config to run crypto on aws, bybi and gate not working 2026-06-24 01:03:49 +00:00
Taha 55ed962c63 Fixing empty TC2 issue, caused by exceptions in _override_theos 2026-06-17 23:10:57 -04:00
arham 68f766e00b fix: param syntax change for args 2026-06-17 10:33:29 -04:00
arham c0252e06c5 fix: fill time nanos stored 2026-06-16 09:58:03 +00:00
John Colvin 4258bd4c82 fix: Use unique ProtoMember ids in orderbook FilterStates
The Tag property had the same id as accounts (5)
2026-06-15 15:29:07 -04:00
John Colvin 4981d70bf2 fix: Return NaN instead of throwing if priceType invalid
It's possible this fixes the ladder freezing bug reported by Ophir
2026-06-15 15:22:28 -04:00
John Colvin b85c249a7d improve: Map nullable Color to Color type in BTSColorExtension
So we don't need "Type={x:Type Color}" in ColorAnimation xaml
2026-06-15 15:21:31 -04:00
John Colvin 6721e07cb6 fix: Use BTSColor markup ext instead of StaticResource 2026-06-15 15:08:28 -04:00
arham 8aa5529218 conf: increased shm mem size 2026-06-02 03:04:55 +00:00
arham ba5fff0168 add: ping service to sdk 2026-06-01 10:28:06 +00:00
arham 976119fbee fix: build was broken, spark.dll being used by another process 2026-06-01 04:31:57 -04:00
arham ed741c0621 fix: sdk computes position from fills instead of using postion service 2026-06-01 08:02:44 +00:00
arham e0cf3c739f Add: backend il3gw fills aggressor field 2026-06-01 07:51:14 +00:00
arham 59e2519a4d fix: fills aggressor flag displayed 2026-06-01 02:12:16 -04:00
arham 15de6f3e3d add: added entries for weekly options cme to defualt cpp conf 2026-05-21 09:48:34 +00:00
John Colvin 2e36f00382 fix: Only set order tag if non-null 2026-05-14 10:43:01 -04:00
John Colvin 9f24a3e7a2 improve: Make sdk demo venue parse case insensitive 2026-05-14 10:43:01 -04:00
John Colvin e8dbf6fe09 vscode: Set Sdk Demo prelaunch task to Build Spark Sdk 2026-05-14 10:43:01 -04:00
John Colvin aa403b83ef improve: Use quantity sign to indicate side in TopOfBookQuoter example 2026-05-14 10:43:01 -04:00
arham c507f20a8e fix: build broken for gcc 2026-05-13 12:33:57 +00:00
arham ea52a4749d conf: update conf 2026-05-13 12:33:44 +00:00
arham 650710b5e2 remove stale config 2026-05-12 11:53:31 +00:00
arham 20f6a29e05 add: conf for cert and prodexit 2026-05-12 11:51:25 +00:00
John Colvin e2f8d4db11 fix build (deploy): Actually include native assets in nupkgs
Native assets (libspark.dll/so) are copied to the outdir for local
builds, and packed in nupkg files for sdk releases. The dest path in the
package is different from the path in the outdir, so we configured
Copy/Pack as separate msbuild items. However, both had the same
Include="@(NativeWindows/LinuxAsset)", so Pack="false" in the Copy item
was overriding Pack="true" in the Pack item.
2026-05-08 14:15:54 -04:00
arham 1812434ddc Fix: front end exe polling decreased from 100ms to 1ms 2026-05-08 15:24:26 +00:00
Taha fd50050064 Position cols in TC2 work again now 2026-05-07 03:57:07 +00:00
arham 6ac43881ae add: add missing tag to default conf 2026-05-07 00:22:28 +00:00
arham 4af1419748 Add: timezone change to chi and std higher ram for multicast compose 2026-05-07 00:22:03 +00:00
arham 7cfcdc3770 fix: ilink3 code generator produces correct code that handles null values 2026-05-07 00:21:10 +00:00
arham d03a705077 fix: ilink3 genereated files updated to handle null values with null instead of ascii zero 2026-05-07 00:20:34 +00:00
arham 58eccad70d fix: fills ui price was zero now it works 2026-05-07 00:19:44 +00:00
arham 5f8ec04ff1 Add: useful tools to prod docker image 2026-05-07 00:19:16 +00:00
arham 05e2107655 Fix: change object pool to detect exe report memory leak from assert to error 2026-05-07 00:18:45 +00:00
arham d41f613e32 FIx: change object pool limit for il3gw for exe reports 2026-05-07 00:17:52 +00:00
arham bcbb598423 Add: better logs and disconnect handling 2026-05-04 20:42:23 +00:00
arham e88c44f9db CME TEMPLATE 9 2026-05-04 16:39:48 -04:00
arham 93625957d1 Fix: CRITICAL CME FIX 2026-04-29 03:58:45 +00:00
159 changed files with 18810 additions and 1212 deletions

No files matched your search

+2 -2
View File
@@ -213,7 +213,7 @@
"type": "coreclr",
"request": "launch",
"program": "",
"preLaunchTask": "Build Api Server (Debug)",
"preLaunchTask": "Build Spark Sdk (Debug)",
"windows": {
"program": "Bts.Spark.Sdk.Demo.exe",
},
@@ -285,7 +285,7 @@
"windows": {
"program": "Spark.exe",
"args": [
"--sparkd",
"--Spark:ConfigServer",
"${input:sparkConfigServer}"
],
"cwd": "build/Debug/bin/net8.0-windows",
+35 -2
View File
@@ -81,6 +81,8 @@ services:
limits:
memory: "${SPARKD__MEMORY:-4gb}"
sparkd-multicast:
environment:
- TZ=America/Chicago
volumes:
- ${SPARKD__DATA_DIR:-sparkd_data}:/home/bts/.local/share/bts/sparkd/data
- ${SPARKD__MD_RECORDING_DIR:-md_recording}:/md_recording
@@ -93,7 +95,7 @@ services:
- CONFIG_DIR=${SPARKD__CONFIG_DIR}
- PYTHON_REQUIREMENTS=${SPARKD__PYTHON_REQUIREMENTS}
- SPARKD_ENTRYPOINT=${SPARKD__SPARKD_ENTRYPOINT}
shm_size: "${SPARKD__SHM_SIZE:-2048mb}"
shm_size: "${SPARKD__SHM_SIZE:-8192mb}"
env_file:
- .env
- ${SPARKD__CONFIG_DIR}/.env
@@ -105,7 +107,38 @@ services:
delay: 3s
resources:
limits:
memory: "${SPARKD__MEMORY:-4gb}"
memory: "${SPARKD__MEMORY:-16gb}"
sparkd-multicast-cert:
environment:
- TZ=America/Chicago
volumes:
- ${SPARKD__DATA_DIR:-sparkd_data}:/home/bts/.local/share/bts/sparkd/data
- ${SPARKD__MD_RECORDING_DIR:-md_recording}:/md_recording
- ${SPARKD__RUNTIME_CONF_DIR:-sparkd_conf}:/home/bts/.local/share/bts/sparkd/conf
build:
dockerfile: src/app_sparkd/Dockerfile
args:
- IMAGE=${SPARKD__IMAGE}
- SOURCE_DIR=${SPARKD__SOURCE_DIR}
- CONFIG_DIR=${SPARKD__CONFIG_DIR}
- PYTHON_REQUIREMENTS=${SPARKD__PYTHON_REQUIREMENTS}
- SPARKD_ENTRYPOINT=${SPARKD__SPARKD_ENTRYPOINT}
shm_size: "${SPARKD__SHM_SIZE:-8192mb}"
env_file:
- .env
- ${SPARKD__CONFIG_DIR}/.env
network_mode: "host"
entrypoint: ${SPARKD__MAIN_ENTRYPOINT}
deploy:
restart_policy:
condition: any
delay: 3s
resources:
limits:
memory: "${SPARKD__MEMORY:-16gb}"
sparkd-tail:
image: bluetradingsystems/linux_dotnet8
volumes:
@@ -19,6 +19,11 @@ RUN add-apt-repository ppa:ubuntu-toolchain-r/test \
RUN apt-get update \
&& apt-get install -y dotnet8
RUN apt-get update && apt-get install -y gdb
RUN apt-get update && apt-get install -y fish
RUN apt-get update && apt-get install -y vim
RUN apt-get update && apt-get install -y less
RUN locale-gen en_US.UTF-8
ARG docker_user="bts"
+17 -25
View File
@@ -187,32 +187,24 @@
Name="Test"
DependsOnTargets="Build"></Target>
<ItemGroup Condition="Exists('@(NativeWindowsAsset)')">
<Content Include="@(NativeWindowsAsset)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>%(FileName)%(Extension)</TargetPath>
<Pack>false</Pack>
</Content>
<Content Include="@(NativeWindowsAsset)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>runtimes/win-x64/native/%(FileName)%(Extension)</TargetPath>
<Pack>true</Pack>
<PackagePath>runtimes/win-x64/native</PackagePath>
</Content>
</ItemGroup>
<Target Name="CopyNativeAssetsToOutput" AfterTargets="Build">
<Copy SourceFiles="@(NativeWindowsAsset)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="True" Condition="Exists('%(NativeWindowsAsset.FullPath)')" />
<Copy SourceFiles="@(NativeLinuxAsset)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="True" Condition="Exists('%(NativeLinuxAsset.FullPath)')" />
</Target>
<ItemGroup Condition="Exists('@(NativeLinuxAsset)')">
<Content Include="@(NativeLinuxAsset)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>%(FileName)%(Extension)</TargetPath>
<Pack>false</Pack>
</Content>
<Content Include="@(NativeLinuxAsset)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>runtimes/linux-x64/native/%(FileName)%(Extension)</TargetPath>
<Pack>true</Pack>
<PackagePath>runtimes/linux-x64/native</PackagePath>
</Content>
<ItemGroup>
<Content Include="@(NativeWindowsAsset)"
Condition="Exists('@(NativeWindowsAsset)')"
Link="%(NativeWindowsAsset.FileName)%(NativeWindowsAsset.Extension)"
CopyToOutputDirectory="PreserveNewest"
Pack="true"
PackagePath="runtimes/win-x64/native/" />
<Content Include="@(NativeLinuxAsset)"
Condition="Exists('@(NativeLinuxAsset)')"
Link="%(NativeLinuxAsset.FileName)%(NativeLinuxAsset.Extension)"
CopyToOutputDirectory="PreserveNewest"
Pack="true"
PackagePath="runtimes/linux-x64/native/" />
</ItemGroup>
</Project>
@@ -0,0 +1,5 @@
SPARKD__CONFIG_DIR=src/app_sparkd/configurations/all_crypto
SPARKD__PYTHON_REQUIREMENTS=src/app_sparkd/configurations/gateio_dev/requirements.txt
SPARKD__DATA_DIR=/home/ubuntu/8000/sparkdata/data
SPARKD__IMAGE=sparkd_symbols
SPARKD__RUNTIME_CONF_DIR=/home/ubuntu/8000/sparkdata/conf
@@ -0,0 +1,11 @@
{
"currencies": [
"USDT"
],
"futures": [
"BTC",
"ETH",
"SOL",
"XRP"
]
}
@@ -0,0 +1,16 @@
{
"currencies": [
"USDT"
],
"futures": [
"BTC",
"ETH",
"SOL",
"XRP"
],
"use_testnet": false,
"logging": {
"console_level": "WARNING",
"file_level": "INFO"
}
}
@@ -0,0 +1,71 @@
PublisherOptions {
value {
network_interface: "0.0.0.0"
heartbeat_ms: 5000
heartbeat_boot_seconds: 60
skip_options: true
hot_initial: 1024
hot_growth: 1024
cold_initial: 8192
cold_growth: 2048
channel_prefix: ""
disable_heartbeat: true
}
}
Logging {
value {
initial_queue_size: 120001
max_queue_growth: 4
file {
default_level: "Info"
subsystem {
name: "md"
level: "Diag"
}
}
}
}
MarketDataConfig {
value {
# python_md_providers {
# venue: VENUE_GATEIO
# module_path: "python_md_providers"
# provider_type: "gateio_md_provider.GateIoMdProvider"
# }
python_md_providers {
venue: VENUE_BYBIT
module_path: "python_interop_plugins"
provider_type: "bybit_md_provider.BybitMdProvider"
}
binance_usds_futures {
num_depth_levels: 5
url_rest_base: "https://fapi.binance.com"
url_ws_base: "wss://fstream.binance.com"
security_criteria {
clauses {
security_types: SECURITY_TYPE_FUTURE
symbols: @SymbolLists.list[Gateio].symbol
}
}
}
okx {
num_depth_levels: 5
url_rest_base: "https://www.okx.com"
url_ws_base: "wss://ws.okx.com:8443"
security_criteria {
clauses {
security_types: SECURITY_TYPE_FUTURE
symbols: @SymbolLists.list[Gateio].symbol
}
}
is_application_ping_pong_enabled: true
application_ping_interval_ms: 20000
application_pong_timeout_ms: 10000
is_protocol_ping_pong_enabled: true
protocol_ping_interval_ms: 60000
protocol_pong_timeout_ms: 30000
}
}
}
@@ -0,0 +1,32 @@
SymbolLists {
value {
list {
key: "Gateio"
symbol: "SOL"
symbol: "BTC"
symbol: "ETH"
symbol: "XRP"
}
}
}
Symbols {
value {
venues {
name: "VENUE_GATEIO"
spot: @SymbolLists.list[Gateio].symbol
}
venues {
name: "VENUE_BYBIT"
future: @SymbolLists.list[Gateio].symbol
}
venues {
name: "VENUE_BINANCE_USDS_FUTURES"
future: @SymbolLists.list[Gateio].symbol
}
venues {
name: "VENUE_OKX"
future: @SymbolLists.list[Gateio].symbol
}
}
}
@@ -0,0 +1,10 @@
SPARKD__CONFIG_DIR=src/app_sparkd/configurations/ophir_cert_live_wb
SPARKD__DATA_DIR=/home/wedbushprod/9000/sparkdata/data
SPARKD__IMAGE=sparkd_symbols
SPARKD__PYTHON_REQUIREMENTS=src/app_sparkd/configurations/ophir_cert_live_wb/requirements.txt
SPARKD__RUNTIME_CONF_DIR=/home/wedbushprod/9000/sparkdata/conf
SPARKD__SHM_SIZE=8g
SPARKD__MEMORY=16g
SPARKD__CONFIG_PORT=9000
SPARKD__QUICKBUS_PORT=9001
SPARKD__SIM_FIX_PORT=9002
@@ -0,0 +1,140 @@
DeploymentType {
value {
type: "dev"
}
}
PublisherOptions {
value {
network_interface_a: 'ens65f1'
network_interface_b: 'ens65f0'
#these get overriden if not entered here
heartbeat_ms: 5000
heartbeat_boot_seconds: 60
skip_options: false
hot_initial: 1024
hot_growth: 1024
cold_initial: 8192
cold_growth: 2048
channel_prefix: ""
disable_heartbeat: false
}
}
CMEOptions {
value {
feed_engine {
name: "mdp3feed"
spin_time: 0
channels_per_thread: 3
data_wait: 10
udp_socket_buffer_size: 268435456
}
channel {
id: "*"
feed_engine: "mdp3feed"
use_tcp_replay: false
recovery_queue_size: 640000
lost_packet_wait_timeout_ms: 10
onixs_log_mode: CME_ONIXS_LOG_MODE_UNSPECIFIED
#max_gap_recovery_cache_size: 4
use_natural_refresh: false
join_snapshot_on_gap: false
}
}
}
Logging {
value {
initial_queue_size: 120001
max_queue_growth: 4
console {
default_level: "Info"
subsystem {
name: "md"
level: "Info"
}
subsystem {
name: "OrderMan"
level: "Info"
}
}
}
}
MarketDataConfig {
value {
glbx {
unique_check_value: 15
use_databento: false
}
# recording {
# venue: VENUE_GLBX
# num_of_seconds_to_run_file_writer: 300
# num_of_seconds_to_run_db_writer : 0
# n_book_levels: 10
# batch_size: 100 #for the db writer
# enable_book_recording: true
# enable_trade_recording: true
# enable_bbo_recording: false
# enable_tbbo_recording: false
# enable_misc_recording: true
#}
}
}
JetFIX {
value {
connection {
name: "maw_004_64"
venue: VENUE_GLBX
socket_connect_host_ip: "69.50.112.177"
socket_connect_port: 44040
sender_compid: "MAW004"
target_compid: "CME"
target_subid: "64"
cme_market_segment_id: 64
start_time: "00:10:00"
end_time: "23:55:00"
start_day: "sunday"
end_day: "friday"
seconds_between_hbs: 15
sender_subid: "BTS"
seconds_to_pause: 2
max_connection_attempts: 100
use_ilink3: true
}
}
}
OrdermanCfg {
value {
executor {
thread_name: "oman_orders"
poll: true
hosted_actor: "MDSource"
hosted_actor: "OrderManager"
hosted_actor: "ExecutionGateway"
}
executor {
thread_name: "oman_slow"
poll: false
hosted_actor: "*"
}
fallback_cust_order_handling_inst: "Y"
}
}
NotSimExecution {
value {
entries {
venue: VENUE_GLBX
}
}
}
Ports {
value {
config_port: 9000
qbus_port: 9001
glbx_sim_fix_port: 9002
ice_sim_fix_port: 9003
replicator_port: 9080
}
}
@@ -1,5 +0,0 @@
SPARKD__CONFIG_DIR=src/app_sparkd/configurations/ophir_demo
SPARKD__DATA_DIR=/home/ubuntu/8000/sparkdata/data
SPARKD__IMAGE=sparkd_symbols
SPARKD__PYTHON_REQUIREMENTS=src/app_sparkd/configurations/ophir_demo/requirements.txt
SPARKD__RUNTIME_CONF_DIR=/home/ubuntu/8000/sparkdata/conf
@@ -1,62 +0,0 @@
JetFIX {
value {
connection {
name: "glbx_1"
venue: VENUE_GLBX
max_order_msgs_per_window: 300
window_duration_msecs: 1000
}
}
}
PublisherOptions {
value {
network_interface: "0.0.0.0"
heartbeat_ms: 5000
heartbeat_boot_seconds: 60
skip_options: true
hot_initial: 1024
hot_growth: 1024
cold_initial: 8192
cold_growth: 2048
channel_prefix: ""
disable_heartbeat: true
}
}
Logging {
value {
initial_queue_size: 120001
max_queue_growth: 4
console {
default_level: "Info"
subsystem {
name: "md"
level: "Warn"
}
subsystem {
name: "OrderMan"
level: "Info"
}
}
file {
default_level: "Info"
subsystem {
name: "OrderMan"
level: "Diag"
}
subsystem {
name: "Http"
level: "Diag"
}
}
}
}
MarketDataConfig {
value {
replay {
venue: VENUE_GLBX
}
}
}
@@ -1,8 +0,0 @@
Symbols {
value {
venues {
name: "VENUE_GLBX"
future: "ES"
}
}
}
@@ -1,5 +1,7 @@
SPARKD__CONFIG_DIR=src/app_sparkd/configurations/ophir_sim_live_wb
SPARKD__CONFIG_DIR=src/app_sparkd/configurations/ophir_prod_live_wb
SPARKD__DATA_DIR=/home/wedbushprod/8000/sparkdata/data
SPARKD__IMAGE=sparkd_symbols
SPARKD__PYTHON_REQUIREMENTS=src/app_sparkd/configurations/ophir_sim_live_wb/requirements.txt
SPARKD__PYTHON_REQUIREMENTS=src/app_sparkd/configurations/ophir_prod_live_wb/requirements.txt
SPARKD__RUNTIME_CONF_DIR=/home/wedbushprod/8000/sparkdata/conf
SPARKD__SHM_SIZE=8g
SPARKD__MEMORY=16g
@@ -0,0 +1,152 @@
DeploymentType {
value {
type: "prod"
}
}
PublisherOptions {
value {
network_interface_a: 'enp193s0f0np0'
network_interface_b: 'enp193s0f1np1'
#these get overriden if not entered here
heartbeat_ms: 5000
heartbeat_boot_seconds: 60
skip_options: false
hot_initial: 1024
hot_growth: 1024
cold_initial: 8192
cold_growth: 2048
channel_prefix: ""
disable_heartbeat: false
}
}
CMEOptions {
value {
feed_engine {
name: "mdp3feed"
spin_time: 0
channels_per_thread: 3
data_wait: 10
udp_socket_buffer_size: 268435456
}
channel {
id: "*"
feed_engine: "mdp3feed"
use_tcp_replay: false
recovery_queue_size: 640000
lost_packet_wait_timeout_ms: 10
onixs_log_mode: CME_ONIXS_LOG_MODE_UNSPECIFIED
#max_gap_recovery_cache_size: 4
use_natural_refresh: false
join_snapshot_on_gap: false
}
}
}
Logging {
value {
initial_queue_size: 120001
max_queue_growth: 4
console {
default_level: "Info"
subsystem {
name: "md"
level: "Info"
}
subsystem {
name: "OrderMan"
level: "Info"
}
}
}
}
MarketDataConfig {
value {
glbx {
unique_check_value: 15
use_databento: false
}
# recording {
# venue: VENUE_GLBX
# num_of_seconds_to_run_file_writer: 300
# num_of_seconds_to_run_db_writer : 0
# n_book_levels: 10
# batch_size: 100 #for the db writer
# enable_book_recording: true
# enable_trade_recording: true
# enable_bbo_recording: false
# enable_tbbo_recording: false
# enable_misc_recording: true
#}
}
}
JetFIX {
value {
connection {
name: "u6p_7qb_54"
venue: VENUE_GLBX
socket_connect_host_ip: "205.209.201.97"
socket_connect_port: 10979
sender_compid: "U6P7QB"
target_compid: "CME"
target_subid: "54"
cme_market_segment_id: 54
start_time: "00:10:00"
end_time: "23:55:00"
start_day: "sunday"
end_day: "friday"
seconds_between_hbs: 15
sender_subid: "BTS"
seconds_to_pause: 1
max_order_msgs_per_window: 300
max_connection_attempts: 2
use_ilink3: true
}
connection {
name: "u6p_7qb_64"
venue: VENUE_GLBX
socket_connect_host_ip: "205.209.201.1"
socket_connect_port: 10979
sender_compid: "U6P7QB"
target_compid: "CME"
target_subid: "64"
cme_market_segment_id: 64
start_time: "00:10:00"
end_time: "23:55:00"
start_day: "sunday"
end_day: "friday"
seconds_between_hbs: 15
sender_subid: "BTS"
seconds_to_pause: 1
max_connection_attempts: 2
max_order_msgs_per_window: 300
use_ilink3: true
}
}
}
OrdermanCfg {
value {
executor {
thread_name: "oman_orders"
poll: true
hosted_actor: "MDSource"
hosted_actor: "OrderManager"
hosted_actor: "ExecutionGateway"
}
executor {
thread_name: "oman_slow"
poll: false
hosted_actor: "*"
}
fallback_cust_order_handling_inst: "Y"
}
}
NotSimExecution {
value {
entries {
venue: VENUE_GLBX
}
}
}
@@ -1,5 +0,0 @@
SPARKD__CONFIG_DIR=src/app_sparkd/configurations/ophir_sim
SPARKD__DATA_DIR=/home/tns/8000/sparkdata/data
SPARKD__IMAGE=sparkd_symbols
SPARKD__PYTHON_REQUIREMENTS=src/app_sparkd/configurations/ophir_sim/requirements.txt
SPARKD__RUNTIME_CONF_DIR=/home/tns/8000/sparkdata/conf
@@ -1,104 +0,0 @@
JetFIX {
value {
connection {
name: "glbx_1"
venue: VENUE_GLBX
max_order_msgs_per_window: 300
window_duration_msecs: 1000
}
}
}
PublisherOptions {
value {
network_interface_a: 'ens3f0np0'
network_interface_b: 'ens3f1np1'
#these get overriden if not entered here
heartbeat_ms: 5000
heartbeat_boot_seconds: 60
skip_options: false
hot_initial: 1024
hot_growth: 1024
cold_initial: 8192
cold_growth: 2048
channel_prefix: ""
disable_heartbeat: false
}
}
CMEOptions {
value {
feed_engine {
name: "mdp3feed"
spin_time: 0
channels_per_thread: 3
data_wait: 10
udp_socket_buffer_size: 268435456
}
channel {
id: "*"
feed_engine: "mdp3feed"
use_tcp_replay: false
recovery_queue_size: 640000
lost_packet_wait_timeout_ms: 10
onixs_log_mode: CME_ONIXS_LOG_MODE_REGULAR
#max_gap_recovery_cache_size: 4
use_natural_refresh: false
join_snapshot_on_gap: false
}
}
}
Logging {
value {
initial_queue_size: 120001
max_queue_growth: 4
console {
default_level: "Info"
subsystem {
name: "md"
level: "Info"
}
subsystem {
name: "OrderMan"
level: "Info"
}
}
}
}
MarketDataConfig {
value {
glbx {
unique_check_value: 15
use_databento: false
}
}
}
# JetFIX {
# value {
# #connection: @JetFIXSimConnection.sim
# connection {
# name: "mlx_004_54"
# venue: VENUE_GLBX
# socket_connect_host_ip: "69.50.112.191"
# socket_connect_port: 42876
# sender_compid: "MLX004"
# target_compid: "CME"
# target_subid: "54"
# cme_market_segment_id: 54
# start_time: "00:10:00"
# end_time: "23:55:00"
# start_day: "sunday"
# end_day: "friday"
# seconds_between_hbs: 30
# sender_subid: "BTS"
# use_ilink3: true
# # raw_data: "S4PMA"
# # auto_fix_seq_num: true
# }
# }
# }
@@ -1,116 +0,0 @@
JetFIX {
value {
connection {
name: "glbx_1"
venue: VENUE_GLBX
max_order_msgs_per_window: 300
window_duration_msecs: 1000
}
}
}
PublisherOptions {
value {
network_interface_a: 'enp193s0f0np0'
network_interface_b: 'enp193s0f0np0'
#these get overriden if not entered here
heartbeat_ms: 5000
heartbeat_boot_seconds: 60
skip_options: false
hot_initial: 1024
hot_growth: 1024
cold_initial: 8192
cold_growth: 2048
channel_prefix: ""
disable_heartbeat: false
}
}
CMEOptions {
value {
feed_engine {
name: "mdp3feed"
spin_time: 0
channels_per_thread: 3
data_wait: 10
udp_socket_buffer_size: 268435456
}
channel {
id: "*"
feed_engine: "mdp3feed"
use_tcp_replay: false
recovery_queue_size: 640000
lost_packet_wait_timeout_ms: 10
onixs_log_mode: CME_ONIXS_LOG_MODE_UNSPECIFIED:q
#max_gap_recovery_cache_size: 4
use_natural_refresh: false
join_snapshot_on_gap: false
}
}
}
Logging {
value {
initial_queue_size: 120001
max_queue_growth: 4
console {
default_level: "Info"
subsystem {
name: "md"
level: "Info"
}
subsystem {
name: "OrderMan"
level: "Info"
}
}
}
}
MarketDataConfig {
value {
glbx {
unique_check_value: 15
use_databento: false
}
recording{
venue: VENUE_GLBX
num_of_seconds_to_run_file_writer: 300
num_of_seconds_to_run_db_writer : 0
n_book_levels: 10
batch_size: 100 #for the db writer
enable_book_recording: true
enable_trade_recording: true
enable_bbo_recording: false
enable_tbbo_recording: false
enable_misc_recording: true
}
}
}
# JetFIX {
# value {
# #connection: @JetFIXSimConnection.sim
# connection {
# name: "mlx_004_54"
# venue: VENUE_GLBX
# socket_connect_host_ip: "69.50.112.191"
# socket_connect_port: 42876
# sender_compid: "MLX004"
# target_compid: "CME"
# target_subid: "54"
# cme_market_segment_id: 54
# start_time: "00:10:00"
# end_time: "23:55:00"
# start_day: "sunday"
# end_day: "friday"
# seconds_between_hbs: 30
# sender_subid: "BTS"
# use_ilink3: true
# # raw_data: "S4PMA"
# # auto_fix_seq_num: true
# }
# }
# }
@@ -109,7 +109,8 @@ struct ExecutionGatewayProvider::PImpl {
return make_gw<ICEExecutionGateway>(conn, conn, *l, o);
case api::VENUE_GLBX:
if (conn.use_ilink3()) {
make_gw<ilink3::GLBXiLink3ExecutionGateway>(conn, conn, *l, o);
return make_gw<ilink3::GLBXiLink3ExecutionGateway>(
conn, conn, *l, o);
}
return make_gw<GLBXExecutionGateway>(conn, conn, *l, o);
case api::VENUE_DERIBIT:
@@ -1,4 +1,5 @@
#include "MDP3Listener.h"
#include "spark/md/log_messages/trade_latency_log.h"
#include "spark/config/protos/market_data.pb.h"
#include "spark/md/protos/co2.pb.h"
#include "spark/utils/datetime.h"
@@ -189,6 +190,9 @@ void on_trade(
PriceSize ps = pricesize_of_price_size(
price, data->handle.security().display_factor, size);
auto date = ll_of_timestamp(onixs_date);
log_trade_latency(
TradeLatencyStage::MDP3_RECEIVED, data->handle.security(), date, ps,
map_aggressor(agside), data->electronic_volume + size, false);
data->handle.add_trade(
ts, ps, date, map_aggressor(agside), data->electronic_volume + size);
}
@@ -287,6 +291,19 @@ void MDP3Listener::heartbeat() {
void MDP3Listener::add_security(
const FixedSecurity &security, co2::TradingStatus status) {
bts_assert(security.numeric_id != 0);
if (security.type == api::SECURITYTYPE_OPTION) {
const auto &option = static_cast<const FixedOptionSecurity &>(security);
const datetime::Date expiration(
option.expiration_year, option.expiration_month,
option.expiration_day);
if (!option_expiration_is_allowed(expiration)) {
LOG(md, Info,
"Skipping MDP3 option {} because it expires beyond the configured "
"cutoff.",
std::string(security.symbol()));
return;
}
}
// Set up the shared memory for this security
auto handle = _producer.add_security(security);
// Add it to the index so it can receive messages
@@ -1,6 +1,8 @@
#pragma once
#include "CMESecurityIndex.h"
#include "spark/config/config.h"
#include "spark/config/protos/toggles.pb.h"
#include "spark/io/JsonDumper.h"
#include "spark/io/json_formatter.h"
#include "spark/md/MBOProcessor.h"
@@ -21,6 +23,28 @@
namespace bts {
namespace md {
inline bool option_expiration_is_allowed(const datetime::Date &expiration) {
const auto max_days =
config::get<config::toggles::Mdp3OptionExpirationCutoff>()
.max_days_until_expiration();
if (expiration.year == 0) {
return true;
}
if (max_days == 0) {
return true;
}
const auto cutoff = core::TimeStamp::now().date() +
core::TimeSpan::from_days(max_days);
const auto expiration_timestamp =
core::TimeStamp(expiration.year, expiration.month, expiration.day);
const bool allowed = expiration_timestamp <= cutoff;
uint32_t cutoff_year;
uint32_t cutoff_month;
uint32_t cutoff_day;
cutoff.to_date(cutoff_year, cutoff_month, cutoff_day);
return allowed;
}
inline co2::AggressorSide map_aggressor(onixs::AggressorSide::Enum agside) {
switch (agside) {
default:
@@ -781,6 +805,11 @@ void MDP3Listener::onOption(
exchange_id, sec_name, Mdp3SecuritySkipReason::ALREADY_ADDED);
return;
}
if (!option_expiration_is_allowed(date_of_maturity(sec))) {
LOG_MDP3_ON_OPTION_SKIP(
exchange_id, sec_name, Mdp3SecuritySkipReason::EXPIRED);
return;
}
LOG_MDP3_ON_OPTION_BEGIN(exchange_id, std::string(sec.symbol().items()));
FixedOptionSecurity fsec;
+1 -1
View File
@@ -160,7 +160,7 @@ namespace mbts.spark {
private bool _compute_midmarket {
get {
return true;
return false;
}
}
+35 -13
View File
@@ -26,6 +26,8 @@ using LadderSettings = mbts.spark.LadderSettings;
namespace bts.shell {
[Export(typeof(IShellDaemon))]
class OrderTicketDaemon : IShellDaemon {
static readonly bool useTicketLadder = true;
readonly IBus _bus;
readonly OrdersModel _model;
readonly UserSettingsHub _settings_hub;
@@ -71,13 +73,23 @@ namespace bts.shell {
void _show_order_ticket(SecurityMD smd) {
var settings_src = _settings_hub.subscribe_default_settings()
.Select(s => s.get_ladder_profile(smd.security)?.value ?? LadderSettings.Default);
.Select(s => s.GetLadderProfile(smd.security)?.value ?? LadderSettings.Default);
var accounts_src = _bus.subscribe(SparkTags.AccountsTag);
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd, accounts_src, settings_src) {
close_on_send = _close_on_send
}
});
_show_order_window(useTicketLadder ? OrderTicketLadderWindow() : OrderTicketWindow());
Window OrderTicketWindow() =>
new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd, accounts_src, settings_src) {
close_on_send = _close_on_send
}
};
Window OrderTicketLadderWindow() =>
new OrderTicketLadder() {
DataContext = new NewOrderTicketLadderVM(
_model, smd, accounts_src, settings_src,
ladderSecurity => _bus.post(SparkTags.ShowLadderTag, ladderSecurity))
};
}
void _show_order_ticket(OrderTicketParams ot_params) {
@@ -114,13 +126,23 @@ namespace bts.shell {
return;
}
var md = _md_map.TryGetValue(ori.order.security_key());
_show_order_window(new OrderModificationDialog {
DataContext = new ExistingOrderTicketVM(_model,
Observable.Return((md, ori.order)),
_bus.subscribe(SparkTags.AccountsTag),
Observable.Return(LadderSettings.Default.calculator_buttons),
md?.quantity_display)
});
var accountsSrc = _bus.subscribe(SparkTags.AccountsTag);
var orderSrc = Observable.Return((md, ori.order));
var calcBnsSrc = Observable.Return(LadderSettings.Default.calculator_buttons);
_show_order_window(useTicketLadder ? OrderTicketLadderWindow() : OrderTicketWindow());
Window OrderTicketWindow() =>
new OrderModificationDialog {
DataContext = new ExistingOrderTicketVM(_model, orderSrc, accountsSrc, calcBnsSrc, md?.quantity_display)
};
Window OrderTicketLadderWindow() =>
new OrderModificationDialog {
DataContext = new ExistingOrderTicketLadderVM(
_model, orderSrc, accountsSrc, calcBnsSrc,
ladderSecurity => _bus.post(SparkTags.ShowLadderTag, ladderSecurity),
md?.quantity_display)
};
}
}
}
@@ -14,16 +14,11 @@ namespace mbts.spark {
/// <summary>
/// Gets the ladder settings profile for a particular security
/// </summary>
public static LadderSettingsProfile? get_ladder_profile(
public static LadderSettingsProfile? GetLadderProfile(
this UserSettings settings, SecurityImmutable security) =>
settings.ladder_profiles.FirstOrDefault(lsp =>
lsp?.security_criteria?.includes(security) ?? false);
public static IObservable<LadderSettingsProfile?> subscribe_ladder_profile(
this UserSettingsHub settings_hub, SecurityImmutable security) =>
settings_hub.subscribe_default_settings().Select(s =>
s.get_ladder_profile(security));
/// <summary>
/// Gets the default account for a particular security (from the
/// <see cref="LadderSettingsProfile"/> associated with that security)
@@ -31,7 +26,7 @@ namespace mbts.spark {
public static IObservable<AccountTuple?> subscribe_default_account(
this UserSettingsHub settings_hub, IBus bus, SecurityImmutable security) {
var acc_id_src = settings_hub.subscribe_default_settings().Select(s =>
s.get_ladder_profile(security)?.value?.default_account);
s.GetLadderProfile(security)?.value?.default_account);
return acc_id_src.CombineLatest(
bus.subscribe(SparkTags.AccountsTag),
(acc_id, accs) => acc_id.HasValue && accs.TryGetValue(acc_id.Value, out var acc_tup) ?
@@ -1,10 +1,10 @@
using bts;
using bts.api;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.extensions;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
@@ -23,6 +23,7 @@ namespace mbts.ui.spark {
/// </summary>
partial class MarketViewView : UserControl {
public static readonly RoutedCommand CellClickCommand = new RoutedCommand();
static readonly bool useTicketLadder = true;
MarketViewVM _vm;
DIYGridPopupManager _popup_man_backing;
@@ -92,7 +93,7 @@ namespace mbts.ui.spark {
void _show_mini_orderbook(IReadOnlyList<SecurityImmutable> securities, Columns col) {
var (vm, context, title, fg) = _vm.orderbook_params(securities, col);
_show_popup(vm, new AuxiliaryDataHeader {
_show_popup(vm, new OrderAuxiliaryPopupHeader {
context = context,
title = title,
title_foreground = fg
@@ -104,7 +105,7 @@ namespace mbts.ui.spark {
void _show_mini_fills(IEnumerable<SecurityImmutable> securities, Columns col) {
var (vm, context, title, fg) = _vm.fills_params(securities, col);
_show_popup(vm, new AuxiliaryDataHeader {
_show_popup(vm, new OrderAuxiliaryPopupHeader {
context = context,
title = title,
title_foreground = fg
@@ -114,29 +115,40 @@ namespace mbts.ui.spark {
async void _show_order_ticket(
SecurityMD security_md, double price = double.NaN, double size = 0) {
var resources = new OrderTicketResources();
resources.MergedDictionaries.Add(new AuxiliaryDataResources());
resources.MergedDictionaries.Add(new OrderAuxiliaryPopupResources());
var account = await _vm.get_default_account(security_md.security);
var def = _model.factory.order(security_md.security, account);
var calc_buttons = LadderSettings.Default.calculator_buttons;
var ticket_vm = new NewOrderTicketVM(
_model,
security_md,
_bus.subscribe(SparkTags.AccountsTag),
new(def),
price,
size,
Observable.Return(calc_buttons));
var accounts = _bus.subscribe(SparkTags.AccountsTag);
var (vm, header) = OrderTicketFactory();
_show_popup(
ticket_vm,
new OrderTicketHeader { bbo = ticket_vm.bbo },
vm,
header,
resources,
view => Behavior.SetAutoFocusTargetProvider(view, AutoFocusTargetProvider.TypeProvider(typeof(TextBox), view)));
(ViewModelBase, object) OrderTicketFactory() {
if (useTicketLadder) {
var vm = new NewOrderTicketLadderVM(
_vm.orders_model, security_md, accounts, new(def), price, size,
Observable.Return(calc_buttons),
ladderSecurity => _bus.post(SparkTags.ShowLadderTag, ladderSecurity));
var header = new OrderTicketLadderHeader() {
Title = security_md.security.ToString()
};
return (vm, header);
} else {
var vm = new NewOrderTicketVM(_model, security_md, accounts, new(def), price, size, Observable.Return(calc_buttons));
var header = new OrderTicketHeader { bbo = vm.bbo };
return (vm, header);
}
}
}
void _show_popup(object content, object header) {
var resources = new AuxiliaryDataResources();
var resources = new OrderAuxiliaryPopupResources();
resources.MergedDictionaries.Add(new AutoHideScrollbarResources());
_show_popup(content, header, resources);
}
@@ -148,9 +160,9 @@ namespace mbts.ui.spark {
Header = header,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Resources = resources ?? new AuxiliaryDataResources()
Resources = resources ?? new OrderAuxiliaryPopupResources()
};
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
view.Style = view.Resources[OrderAuxiliaryPopupResources.PopupChildStyleKey] as Style;
init_view?.Invoke(view);
_popup_man.show(view);
}
@@ -78,6 +78,9 @@ namespace mbts.spark {
public Fill.Types.FillStatus fill_status => _fill.FillStatus;
public string fill_key => _fill.FillKey;
public string order_key => _order.Key.ToString();
// public bool is_aggressor => fill_tuple.is_aggressor;
public string display_aggressor_flag_text =>
display_aggressor_flag(_fill.FillAggressorFlag);
public bool is_selected {
get => _is_selected;
set {
@@ -105,6 +108,13 @@ namespace mbts.spark {
AlgoData? _root_algo => fill_tuple.root_algo;
AlgoDefinitionMetadata? _root_algo_meta => fill_tuple.root_algo_meta;
public static string display_aggressor_flag(FillAggressorFlag flag) =>
flag switch {
FillAggressorFlag.Aggressor => "A",
FillAggressorFlag.Passive => "P",
_ => string.Empty
};
public static FillsRowVM update(FillsRowVM r, FillTuple ft) {
using var _ = _change_detector.detect_changes(r);
r._badge_fill = null;
@@ -254,7 +254,7 @@ namespace mbts.spark {
add_col(
Columns.Price,
DefaultColumnHeaders.Price,
r => _get_row(r).security.spec_or_default().format(r.fill.AvgPrice),
r => _get_row(r).security.spec_or_default().format(r.fill.LastPrice),
new DoubleMinMax<FillTuple>(r => r.fill.LastPrice),
DataGridFilterKnownTemplates.DoubleMinMax,
fs => fs.price,
@@ -295,7 +295,7 @@ namespace mbts.spark {
add_col(
Columns.IsAggressor,
DefaultColumnHeaders.IsAggressor,
r => r.is_aggressor ? SpecialCharacters.CheckMark : string.Empty,
r => FillsRowVM.display_aggressor_flag(r.fill.FillAggressorFlag),
dv_filter(new[] { false, true }, r => r.is_aggressor),
DataGridFilterKnownTemplates.YesNo,
fs => fs.is_aggressor,
@@ -27,13 +27,13 @@ namespace Spark.Trading {
_sec_greeks_in = sec_greeks_in;
}
public IObservable<double> get_price_source(FairValueKey key) {
public IObservable<double> get_price_source(FairValueKey key, double? errorPrice = double.NaN) {
return key.type switch {
FairValueLine.PriceType.Bid => bid_source(),
FairValueLine.PriceType.Ask => ask_source(),
FairValueLine.PriceType.Mid => mid_source(),
FairValueLine.PriceType.Theo => theo_source(),
_ => throw new NotImplementedException()
_ => errorPrice.HasValue ? Observable.Return(errorPrice.Value) : throw new NotImplementedException()
};
IObservable<double> bid_source() =>
@@ -1,4 +1,4 @@
using bts;
using bts;
using bts.api;
using bts.api.md.auxiliary;
using bts.collections;
@@ -15,7 +15,6 @@ using mbts.utils;
using Spark.Core.Collections;
using Spark.Core.Disposables;
using Spark.Core.Math;
using System;
using System.Collections.Generic;
@@ -36,8 +35,9 @@ namespace mbts.spark {
using RegionalTickFactors = user.settings.RegionalTickFactors;
partial class LadderGridHost : Control {
static readonly int _initial_row_count;
static readonly bool _show_scrollbar;
const int _min_row_count = 501;
const int _max_row_count = 2001;
static readonly bool _show_scrollbar = true;
readonly VisualHost _visual_host = new();
readonly LadderOrdersHost _ordershost;
@@ -80,13 +80,11 @@ namespace mbts.spark {
LadderVM _vm;
LadderTactileAdorner _tactile_adorner;
ulong _prices_updated;
ulong lastTradeTime;
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,
};
LadderPriceList _pending_price_list = null;
IList<double> _price_list;
IComparer<double> _price_comparer;
double _last_origin_price;
readonly MarketBreakLines _mkt_break_lines;
@@ -107,7 +105,6 @@ namespace mbts.spark {
static LadderGridHost() {
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
_show_scrollbar = knobs.ShowScrollbar;
_initial_row_count = (int)knobs.ScrollableTickWindowSize;
}
public LadderGridHost() {
@@ -132,33 +129,7 @@ namespace mbts.spark {
_render_grid_background();
_init_tactile_adorner();
}
IDisposable scroll_viewer_hook = null;
loaded_hook = Disposable.Create(
() => {
_scroll_viewer_source.value_changed -= on_scroll_viewer;
Disposable.dispose(ref scroll_viewer_hook);
});
on_scroll_viewer(_scroll_viewer);
void on_scroll_viewer(ScrollViewer new_value) {
Disposable.dispose(ref scroll_viewer_hook);
if (new_value is null) {
return;
}
scroll_viewer_hook = _connect_scroll_viewer(new_value);
delayed_init();
// This is a bit of a hack: the scroll viewer drifts away from the intended VerticalOffset
// unless it is done after a delay.
async void delayed_init() {
await Task.Delay(100);
await Dispatcher.InvokeAsync(() => {
_pending_scroll_to_origin = true;
_refresh_prices();
_refresh_all_visuals();
scroll_to_pending_target();
});
}
}
_scroll_to_center();
};
recenter_command = new DelegateCommand(_scroll_to_center);
DataContextChanged += (s, e) => _connect_vm();
@@ -282,26 +253,6 @@ namespace mbts.spark {
set => SetValue(PIQColumnWidthProperty, value);
}
#region TopTick
public static readonly DependencyProperty TopTickProperty =
WPFHelper.create_dp<int, LadderGridHost>(
nameof(TopTick),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => {
if (o._price_list is null) {
return;
}
o._set_scrollbar_range(high_tick: v);
o._refresh_prices();
o._refresh_all_visuals();
o._ordershost.refresh_orders();
});
public int? TopTick {
get => GetValue(TopTickProperty) as int?;
set => SetValue(TopTickProperty, value);
}
#endregion
#region CanUserSendOrders
public static readonly DependencyProperty CanUserSendOrdersProperty =
@@ -392,7 +343,7 @@ namespace mbts.spark {
}
}
int _row_count => _price_list?.Count ?? 0;
int _row_count = _min_row_count;
bts.api.domain.TickDisplaySettingsImmutable _qty_display =>
_vm is null ?
@@ -425,64 +376,17 @@ namespace mbts.spark {
scroll_to_price(_mkt_break_lines.origin);
public bool scroll_to_price(double price) {
if (price.is_nan()) {
if (_scroll_viewer == null || _price_list.is_null_or_empty())
return false;
}
var tick = _spec.tickdiff(price, 0);
if (tick.HasValue) {
return scroll_to_tick(tick.Value);
}
return false;
}
double _get_target_top_tick(int mid_viewport_tick) {
var curr_rel_off = _scroll_viewer.VerticalOffset + (_scroll_viewer.ViewportHeight / 2.0);
var tgt_rel_off = (_price_list.high_tick - mid_viewport_tick) * _settings.row_height;
var tick_diff = (curr_rel_off - tgt_rel_off) / _settings.row_height;
return _price_list.high_tick + tick_diff;
}
public bool scroll_to_tick(long tick) {
if (tick > int.MaxValue) {
_scroll_to_tick_log_throttle.Execute(() =>
Log.Ui.error($"Unable to scroll to tick {tick} because it exceeds int.MaxValue"));
return false;
}
return scroll_to_tick((int)tick);
}
public bool scroll_to_tick(int tick) {
if (_vm is null || _settings is null || _scroll_viewer is null) {
return false;
}
// var center_offset_ticks = (int)(_scrollViewer.ScrollableHeight / (2.0 * _settings.row_height));
// The scrollbar's vertical offset won't change. Find the 'top tick' that will put the
// target `tick` in the center of the viewport.
// var center_offset_diff = _scrollViewer.VerticalOffset - (_scrollViewer.ScrollableHeight / 2.0);
// var tick_offset = center_offset_ticks
// var tick_offset = (int)(_scrollViewer.ViewportHeight / (2.0 * _settings.row_height));
// The scrollbar's vertical offset won't change. Find the 'top tick' that will put the
// target `tick` in the center of the viewport.
checked {
var top_tick = (int)_get_target_top_tick(tick);
if (_settings.animate_recenter) {
var anim = new Int32Animation {
From = (int)_price_list.high_tick,
To = top_tick,
Duration = TimeSpan.FromSeconds(0.15)
};
var sb = new Storyboard();
sb.Children.Add(anim);
Storyboard.SetTarget(anim, this);
Storyboard.SetTargetProperty(anim, new PropertyPath(TopTickProperty));
sb.Begin();
} else {
_set_scrollbar_range(high_tick: top_tick);
_refresh_prices();
if (_spec.tickdiff(_price_list[0], price) is long target_row) {
int visible_row_count = (int)(_scroll_viewer.ActualHeight / _settings.row_height);
int curr_row = visible_row_count + (int)(_scroll_viewer.VerticalOffset / _settings.row_height);
if ((target_row > curr_row && target_row < _row_count) || (target_row < (curr_row - visible_row_count) && target_row >= 0)) {
_scroll_to_row((int)target_row);
return true;
}
}
return true;
return false;
}
public IObservable<ColumnType> subscribe_hover_column() =>
@@ -564,67 +468,6 @@ namespace mbts.spark {
}
}
IDisposable _connect_scroll_viewer(ScrollViewer scroll_viewer) {
if (scroll_viewer is null) {
return null;
}
const int scroll_tick_padding = 10;
// const double scroll_epsilon = 1E-8;
var dispatcher = Dispatcher.CurrentDispatcher;
bool busy = false;
return scroll_viewer.SubscribeScrollChanged(on_scroll_changed);
async void on_scroll_changed(object sender, ScrollChangedEventArgs e) {
if (e.ExtentHeight == e.ViewportHeight || busy) {
return;
}
busy = true;
await dispatcher.InvokeAsync(ensure_tick_padding, DispatcherPriority.Background);
busy = false;
void ensure_tick_padding() {
if (_price_list is null || _settings is null) {
return;
}
var pct = _scroll_viewer.VerticalOffset / _scroll_viewer.ScrollableHeight;
var new_pl = new LadderPriceList(_price_list);
var stp = scroll_tick_padding * _settings.row_height;
if (pct == 0) {
if (_scroll_viewer.ExtentHeight - _scroll_viewer.ViewportHeight < stp) {
new_pl.extend_up(scroll_tick_padding);
} else {
new_pl.shift_up(scroll_tick_padding);
}
_price_dirty = true;
}
if (pct == 1) {
if (_scroll_viewer.ExtentHeight - _scroll_viewer.ViewportHeight < stp) {
new_pl.extend_down(scroll_tick_padding);
} else {
new_pl.shift_down(scroll_tick_padding);
}
_price_dirty = true;
}
if (new_pl != _price_list) {
_pending_price_list = new_pl;
}
_ordershost.refresh_orders();
}
}
}
void _set_scrollbar_range(int? high_tick = null, int? length = null) {
if (high_tick.HasValue) {
_price_list.high_tick = high_tick.Value;
}
if (length.HasValue) {
_price_list.length = length.Value;
}
_mkt_break_lines.invalidate_indices();
_mkt_break_lines.refresh();
_refresh_all_visuals();
}
void _send_parameterized_order(Point point, Side side) {
Disposable.dispose(ref _parameterized_order_popup);
void on_cannot_send(string reason) {
@@ -762,10 +605,7 @@ namespace mbts.spark {
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
_vm?.recenter_all_ladders();
} else {
var tgt = _spec.tickdiff(_mkt_break_lines.origin, 0.0);
if (tgt.HasValue) {
scroll_to_tick(tgt.Value);
}
_scroll_to_center();
}
}
}
@@ -842,9 +682,14 @@ namespace mbts.spark {
}
internal int _row_for_price(double price) => LadderRender.index_of(_price_list, price, _price_comparer);
internal int _row_for_price(double price) {
if ((_price_list?.Count ?? 0) == 0 || !price.is_real()) {
return -1;
}
return LadderRender.index_of(_price_list, price, _price_comparer);
}
public double y_for_price(double price) => _row_for_price(price) * (_settings?.row_height ?? double.NaN);
// public double y_for_price(double price) => _row_for_price(price) * (_settings?.row_height ?? double.NaN);
#endregion
#region Column Utility Methods
@@ -889,11 +734,30 @@ namespace mbts.spark {
render_market_col(_depth.Bids);
break;
case ColumnType.Price:
_refresh_prices(column);
break;
case ColumnType.Ask:
render_market_col(_depth.Asks);
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
LadderRender.render_prices(
column.visual_tup.visual,
column.visual_tup.glyph_context,
_row_count,
_settings.row_height,
_price_list,
_vm.md.settlement_price,
_settings.net_change == LadderSettings.NetChangeTerms.Ticks,
column.settings,
_spec);
} else {
LadderRender.render_prices(
column.visual_tup.visual,
column.visual_tup.glyph_context,
_row_count,
_settings.row_height,
_price_list,
column.settings,
_spec,
_settings.price_display_format);
}
break;
case ColumnType.Ask: render_market_col(_depth.Asks); break;
case ColumnType.Volume:
_volume_container.mark_dirty();
_refresh_volume_at_price();
@@ -1086,9 +950,8 @@ namespace mbts.spark {
Storyboard.SetTargetProperty(anim, new PropertyPath(Behavior.AnimatedScrollViewerVerticalOffsetProperty));
sb.Begin();
} else {
_scroll_to_row(_price_list.Count / 2);
_scroll_to_row(_row_count / 2);
}
_refresh_frozen_mkt_lines();
}
}
@@ -1102,76 +965,23 @@ 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 last_trade_changed = _vm.md.get_last_trade(ref lastTradeTime, 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 = _create_price_list();
var mid_tick = security.tickdiff(_mkt_break_lines.origin, 0) ?? 0;
if (mid_tick > int.MaxValue) {
Log.Ui.error(
$"Unable to set scrollbar range about mid_tick {mid_tick}: " +
$"tick count exceeds int.MaxValue");
} else {
_set_scrollbar_range(high_tick: (int)mid_tick + (_price_list.Count << 1));
}
_price_dirty = true;
bool prices_changed =
_price_dirty ||
(double.IsNaN(_last_origin_price) && !double.IsNaN(_mkt_break_lines.origin)) ||
_mkt_break_lines.origin >= _price_list[0] || _mkt_break_lines.origin <= _price_list[_price_list.Count - 1];
if (!prices_changed && _prices_out_of_range(out double low, out double high)) {
prices_changed = _refresh_price_range(low, high);
}
if (_price_dirty) {
if (_pending_price_list != null && !_pending_price_list.Equals(_price_list)) {
var prev_price_list = _price_list;
_price_list = _pending_price_list;
_pending_price_list = null;
if (prev_price_list.Count != _price_list.Count) {
_reset_height();
}
// maintain previous visible price range if possible
double percent_scroll_offset = _scroll_viewer.VerticalOffset / _scroll_viewer.ScrollableHeight;
var n_visible_rowsd = _scroll_viewer.ViewportHeight / _settings.row_height;
var n_visible_rows = (int)n_visible_rowsd;
var offset = n_visible_rowsd - n_visible_rows;
var n_rows_to_scroll = _price_list.Count - n_visible_rows - 1;
int prev_target_index = (int)(percent_scroll_offset * n_rows_to_scroll);
// int prev_target_index = (int)(percent_scroll_offset * prev_price_list.Count - 1);
if (prev_price_list.TryGetAt(prev_target_index, out double prev_target_price)) {
int new_target_index = LadderRender.index_of(_price_list, prev_target_price, _price_comparer);
if (_price_list.within_range(new_target_index)) {
// force _scrollViewer to re-calculate ScrollableHeight
_scroll_viewer.UpdateLayout();
double new_vertical_offset = new_target_index * _settings.row_height;
// double new_vertical_offset = ((double)new_target_index / _price_list.Count) * _scrollViewer.ScrollableHeight;
_scroll_viewer.ScrollToVerticalOffset(new_vertical_offset);
}
}
}
_refresh_prices();
scroll_to_pending_target();
if (prices_changed) {
_refresh_price_list();
_refresh_all_visuals();
} else {
scroll_to_pending_target();
if (_mkt_break_lines.bid_dirty)
_refresh_bid_mkt_break();
if (_mkt_break_lines.ask_dirty)
@@ -1243,6 +1053,75 @@ namespace mbts.spark {
_settings.price_display_format);
}
static readonly TimeSpan _price_out_of_range_render_interval = TimeSpan.FromSeconds(5);
DateTime _last_price_range_calc_time = DateTime.MinValue;
void _refresh_price_list() {
if (_settings is null || _spec is null || !_mkt_break_lines.origin.is_real()) {
return;
}
var prev_price_list = _price_list;
_price_list = (!(_settings.net_change == LadderSettings.NetChangeTerms.None)) ?
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _vm.md.settlement_price, _settings.net_change == LadderSettings.NetChangeTerms.Ticks, _row_count, _settings.row_height, _settings.price_column, _spec) :
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _row_count, _settings.row_height, _settings.price_column, _spec, _settings.price_display_format);
_ordershost.update_settings(_settings.row_height, _settings.orders_column);
if (prev_price_list != null) {
// maintain previous visible price range if possible
double percent_scroll_offset = _scroll_viewer.VerticalOffset / _scroll_viewer.ScrollableHeight;
int prev_target_index = (int)(percent_scroll_offset * prev_price_list.Count);
if (prev_price_list.TryGetAt(prev_target_index, out double prev_target_price)) {
int new_target_index = LadderRender.index_of(_price_list, prev_target_price, _price_comparer);
if (_price_list.within_range(new_target_index)) {
// force _scroll_viewer to re-calculate ScrollableHeight
_scroll_viewer.UpdateLayout();
double new_vertical_offset = ((double)new_target_index / _price_list.Count) * _scroll_viewer.ScrollableHeight;
_scroll_viewer.ScrollToVerticalOffset(new_vertical_offset);
}
}
}
}
bool _refresh_price_range(double low, double high) {
bool prices_changed = false;
// The market can be wider than the 1001 rows (ticks) we [initially] allot.
// Rendering prices is expensive, and will lock up the ui if done continuously.
// Therefore, ensure a minimum time interval has elapsed before rendering prices again.
var now = DateTime.Now;
if ((now - _last_price_range_calc_time) > _price_out_of_range_render_interval) {
SparkUtils.lazy_warn(!double.IsNaN(_mkt_break_lines.origin) && _mkt_break_lines.origin >= low && _mkt_break_lines.origin <= high,
() => fmt_log_msg($"market break ({_mkt_break_lines.origin}) is unacceptable for the market range ({low} - {high})"));
int prev_row_count = _row_count;
var n_ticks_to_low = (int)(_spec.tickdiff(_mkt_break_lines.origin, low) ?? 0).within_range(int.MinValue, int.MaxValue);
var n_ticks_to_high = (int)(_spec.tickdiff(high, _mkt_break_lines.origin) ?? 0).within_range(int.MinValue, int.MaxValue);
int desired_row_count = Math.Max(n_ticks_to_low, n_ticks_to_high) * 2 + 1;
desired_row_count = Math.Max(desired_row_count, _min_row_count);
_row_count = Math.Min(_max_row_count, desired_row_count);
if (_row_count != prev_row_count) {
if (desired_row_count > _max_row_count)
Log.Ui.warn(fmt_log_msg($"ticks between market range ({low} - {high}) exceeds the maximum number of rows allowed ({_max_row_count})"));
else
Log.Ui.info(fmt_log_msg($"increase row count to {_row_count} to accomodate market range ({low} - {high})"));
prices_changed = true;
_reset_height();
} else {
prices_changed = _mkt_break_lines.origin != _last_origin_price;
}
_last_price_range_calc_time = now;
//
string fmt_log_msg(string msg) =>
$"Ladder {security} (id:{security.Id}, low/high tick:{security.ContractSpec.LowTick}/{security.ContractSpec.HighTick}): {msg}.";
}
return prices_changed;
}
bool _prices_out_of_range(out double low, out double high) {
low = _bids == null ? double.NaN : TickUtils.low_tick(_bids, _asks);
high = _asks == null ? double.NaN : TickUtils.high_tick(_bids, _asks);
return
(!double.IsNaN(low) && _price_list[_price_list.Count - 1] > low) ||
(!double.IsNaN(high) && _price_list[0] < high);
}
void _refresh_all_visuals() {
if (_scroll_viewer is null || _settings is null) {
return;
@@ -1296,50 +1175,36 @@ namespace mbts.spark {
}
void _refresh_session_low_line() {
if (_settings.session_low_line.show) {
_render_session_line(_session_low_vis, _mkt_break_lines.low_render_index ?? -1, _settings.session_low_line.color);
}
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);
}
void _refresh_session_high_line() {
if (_settings.session_high_line.show) {
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index ?? -1, _settings.session_high_line.color);
}
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);
}
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_depth_item);
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);
}
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);
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, _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);
var bidItems = _bids.Select(nps => (nps.price, (double)nps.size, nps.price * nps.size));
var askItems = _asks.Select(nps => (nps.price, (double)nps.size, nps.price * nps.size));
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, bidItems, _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, askItems, _settings.row_height, _spec, _settings.ask_column, _fmt_price_level);
}
/// <summary>
@@ -1408,12 +1273,9 @@ namespace mbts.spark {
void _clear_visuals() {
if (_settings == null)
return;
if (_price_list is null) {
_price_list = new LadderPriceList {
regional_tick = _spec
};
}
_price_list.reset(null, null, _initial_row_count);
_price_list = new double[_row_count];
for (int i = 0; i < _price_list.Count; i++)
_price_list[i] = double.NaN;
Array.Clear(_bids, 0, _bids.Length);
Array.Clear(_asks, 0, _asks.Length);
_price_dirty = true;
@@ -1421,7 +1283,6 @@ namespace mbts.spark {
_refresh_bid_mkt_break();
_refresh_ask_mkt_break();
_refresh_mid_mkt_break();
_refresh_fair_value_lines(true);
_refresh_session_low_line();
_refresh_session_high_line();
_refresh_session_range();
@@ -1431,58 +1292,38 @@ namespace mbts.spark {
#endregion
#region Market Break, Session Low/High Utils
static double _get_top_price(NativePriceSize[] prices) {
if (prices == null) {
return double.NaN;
}
if (prices.Length == 0) {
return double.NaN;
}
if (prices[0].size == 0) {
return double.NaN;
}
return prices[0].price;
}
static double _get_top_price(NativePriceSize[] prices) =>
prices != null && prices.Length > 0 ? prices[0].price : double.NaN;
double _get_bid_market_break() {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
var v = best_bid
.if_nan(_spec.tickdown(best_ask))
.if_nan(_vm?.settlement ?? double.NaN);
return _spec.round_to_tick(v, RoundingMode.AbsDown);
return double.IsNaN(best_bid) ?
double.IsNaN(best_ask) ? _vm.settlement : _spec.tickdown(best_ask) :
best_bid;
}
double _normalize_price(double price) =>
TickUtils.normalize_price(_spec, price);
double _get_ask_market_break() {
if (_vm is null) {
return double.NaN;
}
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
var v = _spec.tickdown(best_ask)
.if_nan(best_bid)
.if_nan(_vm?.settlement ?? double.NaN);
return _spec.round_to_tick(v, RoundingMode.AbsDown);
return double.IsNaN(best_ask) ?
double.IsNaN(best_bid) ? _vm.settlement : best_bid :
_spec.tickdown(best_ask);
}
double _get_mid_market_break() {
double best_bid = _get_top_price(_bids);
double best_ask = _get_top_price(_asks);
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 ?? double.NaN);
return _spec.round_to_tick(v, RoundingMode.AbsDown);
return double.IsNaN(best_bid) ?
double.IsNaN(best_ask) ? _vm.settlement : _spec.tickdown(best_ask) :
double.IsNaN(best_ask) ? best_bid : TickUtils.mid_tick(_spec, best_bid, best_ask);
}
double _get_session_low() => _normalize_price(_get_session_low_raw());
double _get_session_high() => _normalize_price(_get_session_high_raw());
double _get_session_low_raw() => _vm?.md?.session_low ?? double.NaN;
double _get_session_high_raw() => _vm?.md?.session_high ?? double.NaN;
double _get_session_low() => _vm.md.session_low;
double _get_session_high() => _vm.md.session_high;
void _render_mkt_break(DrawingVisual vis, double price, uint color) =>
LadderRender.render_mkt_break(vis, _row_for_price(price), _settings.row_height, color);
@@ -1506,15 +1347,6 @@ namespace mbts.spark {
_settings = _vm?.get_save_state() ?? s;
_pending_scroll_to_origin |= _settings == null;
_invalidate_spec();
if (_price_list != null) {
_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);
_price_vis_tup.glyph_context = glyph_context(s.price_column);
@@ -1598,9 +1430,22 @@ namespace mbts.spark {
// if (_scroll_viewer != null) {
// _scroll_viewer.Background = s.last_column.background.rgb_brush();
// }
scroll_to_pending_target();
visible_columns_changed?.Invoke();
_refresh_all_visuals();
if (_scroll_viewer is not null && _settings is not null) {
_vm.md.get_last_trade(ref lastTradeTime, out _last_trade, out DateTime _, out ulong __);
_vm.md.get_depth(ref _prices_updated, ref _bids, ref _asks, NativePriceHelpers.COCONUT_MAX_DEPTH);
_mkt_break_lines.refresh();
double low = _bids == null ? double.NaN : TickUtils.low_tick(_bids, _asks);
double high = _asks == null ? double.NaN : TickUtils.high_tick(_bids, _asks);
_refresh_price_range(low, high);
_refresh_price_list();
_refresh_all_visuals();
_refresh_piq();
_piq_dirty = false;
_refresh_volume_at_price();
_last_origin_price = _mkt_break_lines.origin;
_price_dirty = false;
}
}
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
@@ -1631,6 +1476,9 @@ namespace mbts.spark {
return impl is null ? value.ToString() : impl(value, price);
}
string _fmt_mkt_quantity(NativePriceSize nps) =>
_fmt_mkt_quantity(nps.size, nps.price);
void _init_tactile_adorner() {
Disposable.dispose(ref _tactile_adorner);
if (_scroll_viewer != null)
@@ -1745,7 +1593,7 @@ namespace mbts.spark {
return null;
}
if (!double.IsNaN(_session_low)) {
_low_index = _owner._price_list.IndexOf(_session_low);
_low_index = LadderRender.index_of(_owner._price_list, _session_low, _owner._price_comparer);
if (_session_low < _owner._price_list.Last()) {
_low_index = _owner._price_list.Count - 1;
}
@@ -1,6 +1,7 @@
using bts.api;
using bts.utils;
using Spark.Core;
using Spark.Core.Diagnostics;
using System;
@@ -30,6 +31,16 @@ namespace mbts.spark {
regional_tick = other.regional_tick;
}
public LadderPriceList(RegionalTickFactors regionalTick, IList<double> prices) {
regional_tick = regionalTick;
_length = prices.Count;
checked {
_high_tick = prices.Count > 0
? (int)regionalTick.tickdiff(prices[0], 0).assert_value()
: 0;
}
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
RegionalTickFactors _regional_tick;
@@ -1,4 +1,4 @@
using bts;
using bts;
using bts.api;
using bts.api.md.auxiliary;
using bts.collections;
@@ -101,6 +101,24 @@ namespace mbts.spark {
settings.font_size, settings.width, row_height, HorizontalAlignment.Right,
-4, VerticalAlignment.Center, row * row_height, true);
public static double[] gen_prices(double mid, int row_count, RegionalTickFactors spec) {
// require odd number
Log.assert(row_count % 2 == 1);
var result = new double[row_count];
int range = row_count / 2;
int middle = row_count / 2;
result[middle] = mid;
double up = mid, down = mid;
// Starting from the middle iterate out to the edges
for (int x = 1; x <= range; ++x) {
up = spec.tickup(up);
down = spec.tickdown(down);
result[middle - x] = up;
result[middle + x] = down;
}
return result;
}
public static GlyphRun[] format_prices(
IList<double> prices,
double reference_price,
@@ -196,6 +214,45 @@ namespace mbts.spark {
}
}
public static double[] render_prices(DrawingVisual price, GlyphContext glyph_context, double mid, double wrt_price, bool format_in_tick_terms, int row_count, double row_height,
LadderColumnSettings settings, RegionalTickFactors spec) {
var dprices = gen_prices(mid, row_count, spec);
render_prices(price, glyph_context, row_count, row_height, dprices, wrt_price, format_in_tick_terms, settings, spec);
return dprices;
}
public static double[] render_prices(DrawingVisual price, GlyphContext glyph_context, double mid, int row_count, double row_height,
LadderColumnSettings settings, RegionalTickFactors spec, LadderSettings.PriceDisplayFormat price_display_format) {
var dprices = gen_prices(mid, row_count, spec);
render_prices(price, glyph_context, row_count, row_height, dprices, settings, spec, price_display_format);
return dprices;
}
public static void render_prices(DrawingVisual price, GlyphContext glyph_context, int row_count, double row_height, IList<double> dprices, double wrt_price, bool format_in_tick_terms, LadderColumnSettings settings, RegionalTickFactors spec) {
var foreground = settings.foreground.rgb_brush();
//var background = new SolidColorBrush(settings.background.rgb_color());
var terms = format_in_tick_terms ? LadderSettings.NetChangeTerms.Ticks : LadderSettings.NetChangeTerms.Dollars;
var fprices = format_prices(dprices, wrt_price, terms, row_height, settings, glyph_context, spec);
using (var dc = price.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, row_count)) {
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
for (int x = 0; x < row_count; ++x)
dc.DrawGlyphRun(foreground, fprices[x]);
}
}
public static void render_prices(DrawingVisual price, GlyphContext glyph_context, int row_count, double row_height, IList<double> dprices, LadderColumnSettings settings, RegionalTickFactors spec, LadderSettings.PriceDisplayFormat price_display_format) {
var foreground = settings.foreground.rgb_brush();
//var background = new SolidColorBrush(settings.background.rgb_color());
var fprices = format_prices(dprices, double.NaN, LadderSettings.NetChangeTerms.Dollars, row_height, settings, glyph_context, spec, price_display_format);
using (var dc = price.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, row_count)) {
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
for (int x = 0; x < row_count; ++x)
dc.DrawGlyphRun(foreground, fprices[x]);
}
}
public static void render_grid(DrawingVisual dv, IEnumerable<LadderGridHost.IColumn> columns, int rowcount, double row_height, UInt32 line_color) {
var height = rowcount * row_height;
var brush = line_color.rgb_brush();
@@ -26,7 +26,7 @@ namespace bts.shell.orderbook {
[ProtoMember(5)]
public string[] accounts;
[ProtoMember(5)]
[ProtoMember(28)]
public string Tag { get; set; }
[ProtoMember(6)]
@@ -407,16 +407,16 @@ namespace bts.shell.pnl {
void _show_auxiliary_popup<T>(T content, string context_desc, string title_desc, Color fg) {
var view = new HeaderedContentControl {
Content = content,
Header = new AuxiliaryDataHeader {
Header = new OrderAuxiliaryPopupHeader {
context = context_desc,
title = title_desc,
title_foreground = fg
},
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Resources = new AuxiliaryDataResources()
Resources = new OrderAuxiliaryPopupResources()
};
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
view.Style = view.Resources[OrderAuxiliaryPopupResources.PopupChildStyleKey] as Style;
_popup_man.show(view);
}
@@ -8,13 +8,15 @@ using bts.api;
using mbts.analytics;
using mbts;
namespace BCG {
namespace BCG
{
/// For each NMSheetSymbol in an NMSheet gets all the
/// data together (theos, position, vol change etc)
/// and fires a callback.
/// This is where all the data is gathered, the rest
/// is all access to this
public class NMSheetSingleOptionMainDataSource : IDisposable {
public class NMSheetSingleOptionMainDataSource : IDisposable
{
private ThreadedBlackboard _threaded_bb;
private readonly ITag<NMSheetDataRequest> _in_bb_main_sheet_request_tag;
private readonly ITag<ExchangeSecurityMap> _in_bb_secmap_tag =
@@ -24,6 +26,7 @@ namespace BCG {
private readonly ITag<OptionTheoInterpolationData> _in_bb_precomputed_sparse_theos_tag;
private readonly ITag<IDictionary<NMSheetExpiration, double>> _in_bb_vol_shift_tag;
private readonly ITag<NMSheetSettings> _in_bb_sheet_settings_tag;
private readonly ITag<IEnumerable<ITradeLogRow>> _in_bb_trade_log_tag = SparkTags.TradeLogTag;
private readonly ITag<VolSurfaceNonVolInput> _in_exp_param_tag = SparkTags.VolSurfaceNonVolInputTag;
private readonly ITag<bool> _in_bb_main_sheet_visibility_tag;
private ITag<SparkTags.Nothing> _in_bb_onefive_value_tag = SparkTags.TC2Tags.VoidTag,
@@ -31,7 +34,6 @@ namespace BCG {
_in_bb_symbol_theo_shift_tag = SparkTags.TC2Tags.VoidTag,
_in_bb_rollover_data_tag = SparkTags.TC2Tags.VoidTag,
_in_bb_presaved_aw2_theos_tag = SparkTags.TC2Tags.VoidTag,
_in_bb_position_tag = SparkTags.TC2Tags.VoidTag,
_in_bb_option_driver_detailed_price_tag = SparkTags.TC2Tags.VoidTag;
private readonly NMSheetSymbol _nmsheet_symbol;
private Action<Pair<NMSheetSymbol, NMSheetSingleOptionData>> _cb;
@@ -40,7 +42,8 @@ namespace BCG {
private string _electronic_equivelant_symbol;
private NMSheetArchivedVolProvider _archived_vol_provider;
public NMSheetSingleOptionMainDataSource(object req_id, ThreadedBlackboard threaded_bb, NMSheetSymbol option, Action<Pair<NMSheetSymbol, NMSheetSingleOptionData>> callback) {
public NMSheetSingleOptionMainDataSource(object req_id, ThreadedBlackboard threaded_bb, NMSheetSymbol option, Action<Pair<NMSheetSymbol, NMSheetSingleOptionData>> callback)
{
_threaded_bb = threaded_bb;
_nmsheet_symbol = option;
_electronic_equivelant_symbol = option.option_name;
@@ -56,7 +59,8 @@ namespace BCG {
_in_bb_sheet_settings_tag = SparkTags.TC2Tags.NMSheetSettingsTag(req_id.ToString());
_in_bb_main_sheet_visibility_tag = SparkTags.TC2Tags.NMSheetVisibilityTag(req_id.ToString());
_archived_vol_provider = new NMSheetArchivedVolProvider(req_id.ToString(), _threaded_bb, option,
() => {
() =>
{
_rollover_data = null;
_compute_and_fire();
});
@@ -76,7 +80,7 @@ namespace BCG {
_threaded_bb.subscribe(_in_bb_rollover_data_tag);
_threaded_bb.subscribe(_in_bb_presaved_aw2_theos_tag);
_threaded_bb.subscribe(_in_exp_param_tag);
_threaded_bb.subscribe(_in_bb_position_tag);
_threaded_bb.subscribe(_in_bb_trade_log_tag);
_threaded_bb.subscribe(_in_bb_sheet_settings_tag);
_threaded_bb.subscribe(_in_bb_onefive_value_tag);
_threaded_bb.subscribe(_in_bb_qs_tag);
@@ -84,15 +88,17 @@ namespace BCG {
_threaded_bb.subscribe(_in_bb_option_driver_detailed_price_tag);
}
private void _react_to_bb(object tag, object value) {
private void _react_to_bb(object tag, object value)
{
if (_in_bb_main_sheet_request_tag.Equals(tag) || _in_bb_secmap_tag.Equals(tag) || _in_bb_live_theos_tag.Equals(tag) || /*_in_bb_live_driver_bbo_tag.Equals(tag) ||*/ _in_bb_precomputed_sparse_theos_tag.Equals(tag) ||
_in_bb_vol_shift_tag.Equals(tag) || _in_bb_symbol_theo_shift_tag.Equals(tag) || _in_exp_param_tag.Equals(tag) || _in_bb_presaved_aw2_theos_tag.Equals(tag) ||
_in_bb_position_tag.Equals(tag) || _in_bb_rollover_data_tag.Equals(tag) || _in_bb_sheet_settings_tag.Equals(tag) || _in_bb_qs_tag.Equals(tag) ||
_in_bb_trade_log_tag.Equals(tag) || _in_bb_rollover_data_tag.Equals(tag) || _in_bb_sheet_settings_tag.Equals(tag) || _in_bb_qs_tag.Equals(tag) ||
_in_bb_main_sheet_visibility_tag.Equals(tag))
_compute_and_fire();
}
private NMSheetDriverData _get_driver(DriverMD d) {
private NMSheetDriverData _get_driver(DriverMD d)
{
double dr_pr = d.get_driver();
if (d.driver_security.HasValue)
return new NMSheetDriverData(d.driver_security.symbol_or_default(),
@@ -102,8 +108,10 @@ namespace BCG {
return new NMSheetDriverData(String.Empty, Venue.Unset, DateTime.MinValue, dr_pr, dr_pr, false);
}
private void _compute_and_fire() {
if (_is_main_sheet_active()) {
private void _compute_and_fire()
{
if (_is_main_sheet_active())
{
var timer = Stopwatch.StartNew();
NMSheetDataRequest data_request = _threaded_bb.read_state(_in_bb_main_sheet_request_tag) ?? new NMSheetDataRequest(new List<Triplet<NMSheetExpiration, bool, double>>());
data_request = NMSheetDataRequest.restrict_for_symbol(data_request, _nmsheet_symbol);
@@ -112,7 +120,8 @@ namespace BCG {
NMSheetSettings sheet_settings = _threaded_bb.read_state(_in_bb_sheet_settings_tag) ?? new NMSheetSettings();
ExpStrikeKeyedData<mbts.spark.TheoPoint> shared_live_theo_data_map = _threaded_bb.read_state(_in_bb_live_theos_tag);
if (data_request != null && live_driver_map != null && shared_live_theo_data_map != null) {
if (data_request != null && live_driver_map != null && shared_live_theo_data_map != null)
{
ExpStrikeKeyedData<mbts.spark.TheoPoint> output_theo_data_map = _limit_to_requested_securities(shared_live_theo_data_map, data_request, sheet_settings);
output_theo_data_map = _adjust_for_freezes(output_theo_data_map, data_request);
_add_vol_shift(output_theo_data_map);
@@ -138,14 +147,17 @@ namespace BCG {
}
}
private bool _is_main_sheet_active() {
private bool _is_main_sheet_active()
{
return _threaded_bb.read_state(_in_bb_main_sheet_visibility_tag, true);
}
private ExpStrikeKeyedData<mbts.spark.TheoPoint> _limit_to_requested_securities(ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_data, NMSheetDataRequest data_request, NMSheetSettings sheet_settings) {
private ExpStrikeKeyedData<mbts.spark.TheoPoint> _limit_to_requested_securities(ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_data, NMSheetDataRequest data_request, NMSheetSettings sheet_settings)
{
HashSet<DateTime> selected_exp_map = new HashSet<DateTime>();
HashSet<double> selected_strike_map = new HashSet<double>(sheet_settings.setting_unselected_strikes);
foreach (Triplet<NMSheetExpiration, bool, double> t in data_request.expiration_request_list) {
foreach (Triplet<NMSheetExpiration, bool, double> t in data_request.expiration_request_list)
{
NMSheetExpiration nm_exp = t.first;
if (nm_exp != null && nm_exp.option_name == _nmsheet_symbol.option_name)
selected_exp_map.Add(nm_exp.option_expiration);
@@ -153,12 +165,14 @@ namespace BCG {
return theo_data.clone_filtered_set(selected_exp_map, true, selected_strike_map, false);
}
private ExpStrikeKeyedData<Triplet<mbts.spark.TheoPoint, uint, uint>> _attach_sec_keys(ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_map) {
private ExpStrikeKeyedData<Triplet<mbts.spark.TheoPoint, uint, uint>> _attach_sec_keys(ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_map)
{
theo_map = theo_map ?? new ExpStrikeKeyedData<mbts.spark.TheoPoint>();
ExchangeSecurityMap sec_map = _threaded_bb.read_state(_in_bb_secmap_tag);
if (sec_map != null)
return sec_map.attach_sec_keys(theo_map, _electronic_equivelant_symbol, _nmsheet_symbol.option_exchange);
else {
else
{
ExpStrikeKeyedData<Triplet<mbts.spark.TheoPoint, uint, uint>> rv = new ExpStrikeKeyedData<Triplet<mbts.spark.TheoPoint, uint, uint>>();
foreach (Triplet<DateTime, double, mbts.spark.TheoPoint> t in theo_map.get_data_linearly())
rv.set_data(t.first, t.second, new Triplet<mbts.spark.TheoPoint, uint, uint>(t.third, 0, 0));
@@ -166,7 +180,8 @@ namespace BCG {
}
}
private ExpStrikeKeyedData<mbts.spark.TheoPoint> _adjust_for_freezes(ExpStrikeKeyedData<mbts.spark.TheoPoint> live_theo_map, NMSheetDataRequest data_request) {
private ExpStrikeKeyedData<mbts.spark.TheoPoint> _adjust_for_freezes(ExpStrikeKeyedData<mbts.spark.TheoPoint> live_theo_map, NMSheetDataRequest data_request)
{
return live_theo_map;
// TODO
// ExpStrikeKeyedData<mbts.spark.TheoPoint> rv = new ExpStrikeKeyedData<mbts.spark.TheoPoint>();
@@ -192,9 +207,11 @@ namespace BCG {
private Dictionary<DateTime, NMSheetDriverData> _get_option_exp_driver_data_map(NMSheetDataRequest data_request,
ExpStrikeKeyedData<mbts.spark.TheoPoint> output_theo_data_map, ExpStrikeKeyedData<mbts.spark.TheoPoint> shared_live_theo_data_map,
Dictionary<DateTime, Triplet<(string, Venue), DateTime, double>> live_driver_map) {
Dictionary<DateTime, Triplet<(string, Venue), DateTime, double>> live_driver_map)
{
Dictionary<DateTime, NMSheetDriverData> rv = new Dictionary<DateTime, NMSheetDriverData>();
foreach (Triplet<NMSheetExpiration, bool, double> theo_request in data_request.expiration_request_list) {
foreach (Triplet<NMSheetExpiration, bool, double> theo_request in data_request.expiration_request_list)
{
DateTime option_expiration = theo_request.first.option_expiration;
/// live driver
var live_driver_info = live_driver_map.GetValueOrDefault(option_expiration)
@@ -208,9 +225,11 @@ namespace BCG {
private Dictionary<DateTime, NMSheetDriverData>
_get_option_exp_driver_data_map(NMSheetDataRequest data_request,
OptionDriverPriceMap drivers) {
OptionDriverPriceMap drivers)
{
var rv = new Dictionary<DateTime, NMSheetDriverData>();
foreach (var tr in data_request.expiration_request_list) {
foreach (var tr in data_request.expiration_request_list)
{
NMSheetExpiration nm_exp = tr.first;
SecurityExpiration exp = tr.first.to_sec_exp();
drivers.exp_price_map.TryGetValue(exp, out DriverMD dr_md);
@@ -222,7 +241,8 @@ namespace BCG {
return rv;
}
private static double _get_driverprice_for_expiration(ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_data_map, DateTime option_expiration) {
private static double _get_driverprice_for_expiration(ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_data_map, DateTime option_expiration)
{
IDictionary<double, mbts.spark.TheoPoint> exp_data = theo_data_map.get_data_for_single_exp(option_expiration);
if (exp_data != null)
foreach (mbts.spark.TheoPoint tp in exp_data.Values)
@@ -230,7 +250,21 @@ namespace BCG {
return Double.NaN;
}
private IDictionary<DateTime, ATMData> _get_atm_data(ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_data) {
private double _get_rep_driver() {
var map =
_threaded_bb.read_state(SparkTags.OptionDriverBBOTag((_nmsheet_symbol.option_name, _nmsheet_symbol.option_exchange)));
if (map == null)
return Double.NaN;
var price_map = map.get_price_map();
if (price_map == null || price_map.Count == 0)
return Double.NaN;
var md = price_map.ElementAt(0).Value;
double pr = md.get_driver();
return pr;
}
private IDictionary<DateTime, ATMData> _get_atm_data(ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_data)
{
NMSheetSettings sheet_settings = _threaded_bb.read_state(_in_bb_sheet_settings_tag) ?? new NMSheetSettings();
VolSurfaceNonVolInput vs_nvinp = _threaded_bb.read_state(_in_exp_param_tag);
var option_exp_roll_data = vs_nvinp
@@ -240,7 +274,7 @@ namespace BCG {
exp.security_type == SecurityType.Option)
?.ToDictionaryCrushed(kv => kv.Key.expiration, kv => kv.Value)
?? new();
IDictionary<DateTime, ATMData> atm_data = OptionATMDataSource.compute_atm_data(_get_onefive(), theo_data, option_exp_roll_data);
IDictionary<DateTime, ATMData> atm_data = OptionATMDataSource.compute_atm_data(_get_onefive(), theo_data, option_exp_roll_data, _get_rep_driver());
if (sheet_settings.setting_atm.select_atm_nearest_strike_only)
foreach (ATMData exp_atm_data in atm_data.Values)
exp_atm_data.reduce_to_nearest_expiration();
@@ -248,17 +282,43 @@ namespace BCG {
}
OptionContractKeyedData<PNLRollOverDataUnit> _rollover_data = null;
private OptionContractKeyedData<PNLRollOverDataUnit> _get_rollover_data() {
private OptionContractKeyedData<PNLRollOverDataUnit> _get_rollover_data()
{
if (_rollover_data == null)
_rollover_data = _archived_vol_provider.get_all_data();
return _rollover_data;
}
private ExpStrikeKeyedData<Tuple<double, double>> _get_position_map() {
return new ExpStrikeKeyedData<Tuple<double, double>>();
private ExpStrikeKeyedData<Tuple<double, double>> _get_position_map()
{
var sheet_settings = _threaded_bb.read_state(_in_bb_sheet_settings_tag);
var risk_account_text = sheet_settings?.setting_layout?.risk_account ?? string.Empty;
var risk_accounts = risk_account_text
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(a => a.Trim())
.Where(a => !string.IsNullOrEmpty(a))
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var trades = _threaded_bb.read_state(_in_bb_trade_log_tag) ?? Enumerable.Empty<ITradeLogRow>();
var option_trades = trades.Where(t =>
t.is_active &&
t.security_type == SecurityType.Option &&
t.symbol == _nmsheet_symbol.option_name &&
t.venue == _nmsheet_symbol.option_exchange &&
(risk_accounts.Count == 0 || risk_accounts.Contains(t.account)));
var pos_data = SecurityPositionData.from_trades(option_trades);
var rv = new ExpStrikeKeyedData<Tuple<double, double>>();
foreach (var (sec_exp, strike_map) in pos_data.option_pos_map)
{
foreach (var (strike, strike_pos) in strike_map)
{
rv.set_data(sec_exp.expiration, strike, Tuple.Create(strike_pos.call, strike_pos.put));
}
}
return rv;
}
private double _get_onefive() {
private double _get_onefive()
{
// object dp_obj = _threaded_bb.sec_thread_retrieve_from_cache(_in_bb_onefive_value_tag);
// if (dp_obj != null) {
// DataPacket dp = dp_obj as DataPacket;
@@ -270,22 +330,29 @@ namespace BCG {
#region Vol Shift
private void _add_vol_shift(ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_data) {
private void _add_vol_shift(ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_data)
{
var vshift_map = _threaded_bb.read_state(_in_bb_vol_shift_tag);
if (vshift_map != null)
add_vol_shift(_nmsheet_symbol, theo_data, vshift_map);
}
internal static void add_vol_shift(NMSheetSymbol apply_to_symbol, ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_data, IDictionary<NMSheetExpiration, double> exp_shift_map) {
foreach (KeyValuePair<NMSheetExpiration, double> p in exp_shift_map) {
if (p.Key.nmsheet_symbol.Equals(apply_to_symbol)) {
internal static void add_vol_shift(NMSheetSymbol apply_to_symbol, ExpStrikeKeyedData<mbts.spark.TheoPoint> theo_data, IDictionary<NMSheetExpiration, double> exp_shift_map)
{
foreach (KeyValuePair<NMSheetExpiration, double> p in exp_shift_map)
{
if (p.Key.nmsheet_symbol.Equals(apply_to_symbol))
{
DateTime option_expiration = p.Key.option_expiration;
double shift = p.Value;
if (shift != 0) {
if (shift != 0)
{
IDictionary<double, mbts.spark.TheoPoint> exp_theos = theo_data.get_data_for_single_exp(option_expiration);
if (exp_theos != null) {
if (exp_theos != null)
{
Dictionary<double, mbts.spark.TheoPoint> shifted_exp_theos = new Dictionary<double, mbts.spark.TheoPoint>();
foreach (KeyValuePair<double, mbts.spark.TheoPoint> strike_tp in exp_theos) {
foreach (KeyValuePair<double, mbts.spark.TheoPoint> strike_tp in exp_theos)
{
mbts.spark.TheoPoint new_tp = (mbts.spark.TheoPoint)strike_tp.Value.Clone();
add_vol_shift(new_tp, shift);
shifted_exp_theos[strike_tp.Key] = new_tp;
@@ -297,7 +364,8 @@ namespace BCG {
}
}
internal static void add_vol_shift(mbts.spark.TheoPoint v, double shift) {
internal static void add_vol_shift(mbts.spark.TheoPoint v, double shift)
{
v.call += v.cvega * shift;
v.put += v.pvega * shift;
}
@@ -305,7 +373,8 @@ namespace BCG {
#endregion
#region IDisposable
public void Dispose() {
public void Dispose()
{
_threaded_bb.data_callback -= _react_to_bb;
}
#endregion
@@ -5,6 +5,7 @@ using mbts.ui;
using mbts;
using BCG.Utilities;
using mbts.spark;
using mbts.ui.converters;
namespace BCG {
/// <summary>
@@ -34,8 +35,10 @@ namespace BCG {
}
internal static IDictionary<DateTime, ATMData> compute_atm_data(
double one_five, ExpStrikeKeyedData<TheoPoint> theo_data, IDictionary<DateTime, double> opt_exp_rolls) {
double one_five, ExpStrikeKeyedData<TheoPoint> theo_data, IDictionary<DateTime, double> opt_exp_rolls,
double rep_driver) {
IDictionary<DateTime, double> exp_vs_drivers = _get_computed_drivers(theo_data);
exp_vs_drivers = exp_vs_drivers.ToDictionary(kv => kv.Key, kv => rep_driver);
IDictionary<DateTime, ATMData> rv = new Dictionary<DateTime, ATMData>();
if (one_five != 0)
foreach (DateTime exp in new List<DateTime>(exp_vs_drivers.Keys))
@@ -0,0 +1,236 @@
#nullable enable
namespace mbts.spark;
using bts;
using bts.api;
using bts.api.domain;
using bts.api.orderman;
using mbts.co2;
using mbts.ui;
using mbts.ui.shell;
using Spark.Core.Disposables;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Input;
using System.Windows.Threading;
class ExistingOrderTicketLadderVM : DisposableViewModelBase, IRequestViewClose {
const ushort numPriceLevels = 5;
readonly Property<SecurityImmutable> security;
readonly Property<TickDisplaySettingsImmutable> priceDisplay;
readonly Property<TickDisplaySettingsImmutable> quantityDisplay;
readonly Property<user.settings.RegionalTickFactors> priceTick;
readonly ObservableCollection<OrderTicketLadderPriceLevelVM> bids = [];
readonly ObservableCollection<OrderTicketLadderPriceLevelVM> asks = [];
readonly Property<double> price, currentPrice;
readonly Property<uint> currentQuantity;
readonly Property<AccountTuple> currentAccount;
readonly Property<TimeInForce> currentTimeInForce;
readonly Property<CustomOrderDefinition.CustomOneofCase> currentOrderType;
readonly Property<bool> priceDirty, quantityDirty, accountDirty, timeInForceDirty, orderTypeDirty;
Side side = Side.Buy;
public ExistingOrderTicketLadderVM(
OrdersModel model,
IObservable<(SecurityMD? SecurityMd, OrderRecordImmutable Order)> orderSource,
IObservable<IReadOnlyDictionary<uint, AccountTuple>> accountsSource,
IObservable<double[]?> calculatorButtons,
Action<SecurityMD> showLadder,
TickDisplaySettingsImmutable? quantityDisplay = null
) {
SecurityMD? currentSecurityMd = null;
uint? key = null;
DispatcherTimer? mdPollTimer = null;
var nativeBids = new NativePriceSize[numPriceLevels];
var nativeAsks = new NativePriceSize[numPriceLevels];
for (int i = 0; i < numPriceLevels; i++) {
nativeBids[i] = NativePriceSize.NO_MARKET;
nativeAsks[i] = NativePriceSize.NO_MARKET;
Bids.Add(new(Side.Buy));
Asks.Add(new(Side.Sell));
}
var secMdSrc = orderSource.Where(ot => ot.SecurityMd.HasValue).Select(ot => ot.SecurityMd!.Value);
security = backing(nameof(Security), secMdSrc.Select(smd => smd.security), bts.api.Security.Defaults.SecurityImmutable);
priceTick = backing(nameof(PriceTick), user.settings.RegionalTickFactors.Default);
PropertiesVm = new OrderPropertiesEditorVM(
model,
secMdSrc,
accountsSource,
orderSource.Select(ot => new InlineOrderProperties(model, ot.Order.Def)));
this.quantityDisplay = backing(nameof(QuantityDisplay), quantityDisplay ?? bts.api.Security.Defaults.quantity_display_settingsi);
priceDisplay = backing(nameof(PriceDisplay), bts.api.Security.Defaults.price_display_settingsi);
SizeCalculator = new(0, calculatorButtons);
SetPriceCmd = new DelegateCommand<double>(SetPrice);
ShowLadderCmd = new DelegateCommand(() => {
if (currentSecurityMd.HasValue)
showLadder(currentSecurityMd.Value);
});
price = backing(nameof(Price), double.NaN);
currentPrice = backing(nameof(CurrentPrice), orderSource.Select(ort => ort.Order.State.Price));
currentQuantity = backing(nameof(CurrentQuantity), orderSource.Select(ort => ort.Order.State.Quantity));
currentAccount = backing(nameof(CurrentAccount), orderSource.Select(ort => new AccountTuple(ort.Order.Def.AccountId, ort.Order.Def.Account)));
currentTimeInForce = backing(nameof(CurrentTimeInForce), orderSource.Select(ort => ort.Order.Def.TimeInForce));
currentOrderType = backing(nameof(CurrentOrderType), orderSource.Select(ort => ort.Order.Def.CustomDef.CustomCase));
//
priceDirty = backing(
nameof(PriceDirty),
price.observe_current(true).CombineLatest(
currentPrice.observe_current(true),
(val, curr) => !double.IsNaN(val) && val != curr));
quantityDirty = backing(
nameof(QuantityDirty),
SizeCalculator.observe_actual_value()
.CombineLatest(currentQuantity.observe_current(true),
(val, curr) => val.HasValue && val.Value != curr));
accountDirty = backing(
nameof(AccountDirty),
PropertiesVm.observe_account(true)
.CombineLatest(currentAccount.observe_current(true),
(val, curr) => val != curr));
timeInForceDirty = backing(
nameof(TimeInForceDirty),
PropertiesVm.observe_time_in_force(true)
.CombineLatest(currentTimeInForce.observe_current(true),
(val, curr) => val != curr));
orderTypeDirty = backing(
nameof(OrderTypeDirty),
PropertiesVm.observe_order_type(true)
.CombineLatest(currentOrderType.observe_current(true),
(val, curr) => val != null && val.order_type != curr));
//
var priceDirtySrc = priceDirty.Select(vals => vals.newval).StartWith(false);
var quantityDirtySrc = quantityDirty.Select(vals => vals.newval).StartWith(false);
var sendOrderCmd = new ObservantCommand(SendOrder);
SendOrderCmd = sendOrderCmd;
var canSendOrderSrc = priceDirty.observe_current(true)
.CombineLatest(quantityDirty.observe_current(true), (pd, sd) => pd || sd)
.DistinctUntilChanged();
var isDirtySrc = Observable.Merge(priceDirty, quantityDirty, accountDirty, timeInForceDirty, orderTypeDirty);
_dispose = Disposable.Create(
SizeCalculator,
PropertiesVm,
secMdSrc.OnDispatcher().Subscribe(OnSecurityMd),
orderSource.OnDispatcher().Subscribe(ort => {
key = ort.Order.Key;
if (DoubleHelper.IsNaNOrInfinity(Price))
Price = ort.Order.State.Price;
if (!SizeCalculator.value.HasValue || !SizeCalculator.value_setter_called)
SizeCalculator.bypass_value_setter(ort.Order.State.Quantity, true);
}),
isDirtySrc.OnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(DirtyItemsText))),
canSendOrderSrc.OnDispatcher().Subscribe(sendOrderCmd));
void SetPrice(double price) {
Price = price;
RaisePropertyChanged(nameof(Price));
}
void SendOrder() {
if (!key.HasValue) {
return;
}
if (priceDirty.Value) {
if (quantityDirty.Value) {
model.update_order(key.Value, Price, SizeCalculator.value!.Value).ignore();
} else {
model.update_order_price(key.Value, Price).ignore();
}
} else if (quantityDirty.Value) {
model.update_order_size(key.Value, SizeCalculator.value!.Value).ignore();
} else {
SizeCalculator.flash_error();
flash_error(nameof(Price)).ignore();
return;
}
RequestClose?.Invoke(this, new EventArgs());
}
void OnSecurityMd(SecurityMD smd) {
currentSecurityMd = smd;
priceTick.set(smd.security.regional_ticks());
priceDisplay.set(smd.price_display);
this.quantityDisplay.set(smd.quantity_display);
SizeCalculator.display_settings = smd.quantity_display;
ulong lastMdPollTime = 0;
mdPollTimer?.Stop();
mdPollTimer = new DispatcherTimer(TimeSpan.FromSeconds(0.1), DispatcherPriority.DataBind, PollMd, Dispatcher.CurrentDispatcher);
void PollMd(object? sender, EventArgs e) {
if (smd.md.get_depth(ref lastMdPollTime, ref nativeBids, ref nativeAsks, numPriceLevels)) {
for (int i = 0; i < numPriceLevels; i++) {
asks[numPriceLevels - i - 1].Price = nativeAsks[i].price;
asks[numPriceLevels - i - 1].Quantity = nativeAsks[i].size;
}
for (int i = 0; i < numPriceLevels; i++) {
bids[i].Price = nativeBids[i].price;
bids[i].Quantity = nativeBids[i].size;
}
}
}
}
}
public event EventHandler? RequestClose;
public SecurityImmutable Security => security.Value;
public string Title => Security.ToString() ?? string.Empty;
public double Price { get; set; }
public user.settings.RegionalTickFactors PriceTick => priceTick.Value;
public TickDisplaySettingsImmutable PriceDisplay => priceDisplay.Value;
public TickDisplaySettingsImmutable QuantityDisplay => quantityDisplay.Value;
public CalculatorLogic<double> SizeCalculator { get; }
public Side Side {
get => side;
set => Set(ref side, value);
}
public OrderPropertiesEditorVM PropertiesVm { get; }
public ObservableCollection<OrderTicketLadderPriceLevelVM> Bids => bids;
public ObservableCollection<OrderTicketLadderPriceLevelVM> Asks => asks;
public double CurrentPrice => currentPrice.Value;
public uint CurrentQuantity => currentQuantity.Value;
public AccountTuple CurrentAccount => currentAccount.Value;
public TimeInForce CurrentTimeInForce => currentTimeInForce.Value;
public CustomOrderDefinition.CustomOneofCase CurrentOrderType => currentOrderType.Value;
public bool PriceDirty => priceDirty.Value;
public bool QuantityDirty => quantityDirty.Value;
public bool AccountDirty => accountDirty.Value;
public bool TimeInForceDirty => timeInForceDirty.Value;
public bool OrderTypeDirty => orderTypeDirty.Value;
public bool CanChangeAccount { get; } = false;
public bool CanChangeTimeInForce { get; } = false;
public bool CanChangeOrderType { get; } = false;
public string DirtyItemsText {
get {
var dirty_items = dirtyItems().ToList();
if (dirty_items.Count == 0) {
return "(no changes)";
}
return "change " + string.Join(", ", dirty_items);
}
}
public bool CloseOnSend { get; set; }
public ICommand SendOrderCmd { get; }
public ICommand SetPriceCmd { get; }
public ICommand ShowLadderCmd { get; }
IEnumerable<string> dirtyItems() {
if (priceDirty.Value) yield return "price";
if (quantityDirty.Value) yield return "quantity";
if (accountDirty.Value) yield return "account";
if (timeInForceDirty.Value) yield return "tif";
if (orderTypeDirty.Value) yield return "type";
}
}
@@ -0,0 +1,298 @@
#nullable enable
namespace mbts.spark;
using bts;
using bts.api;
using bts.api.domain;
using bts.api.orderman;
using bts.collections;
using bts.utils;
using mbts.co2;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.spark;
using Spark.Core.Disposables;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
class NewOrderTicketLadderVM : DisposableViewModelBase, IOrderModificationLadderVM {
const ushort numPriceLevels = 5;
const uint numPriceRowsPerSide = 500;
readonly Property<SecurityImmutable> security;
readonly Property<string> title;
readonly Property<TickDisplaySettingsImmutable> priceDisplay;
readonly Property<TickDisplaySettingsImmutable> quantityDisplay;
readonly Property<user.settings.RegionalTickFactors> priceTick;
readonly ObservableCollection<OrderTicketLadderPriceLevelVM> priceLevels = [];
Side side = Side.Buy;
public NewOrderTicketLadderVM(
OrdersModel model,
SecurityMD securityMd,
IObservable<IReadOnlyDictionary<uint, AccountTuple>> accountsSource,
OrderDefinitionImmutable def,
double price,
double size,
IObservable<double[]> calculatorButtons,
Action<SecurityMD> showLadder)
: this(
model,
Observable.Return(securityMd),
accountsSource,
Observable.Return(new InlineOrderTicketProperties(model, def, price, size)),
calculatorButtons,
showLadder) { }
public NewOrderTicketLadderVM(
OrdersModel model,
SecurityMD securityMd,
IObservable<IReadOnlyDictionary<uint, AccountTuple>> accountsSource,
IObservable<LadderSettings> settings,
Action<SecurityMD> showLadder)
: this(
model,
Observable.Return(securityMd),
accountsSource,
settings.Select(s => new InlineOrderTicketProperties(model, s)),
settings.Select(s => s.calculator_buttons),
showLadder) { }
public NewOrderTicketLadderVM(
OrdersModel model,
IObservable<SecurityMD> securityMdSource,
IObservable<IReadOnlyDictionary<uint, AccountTuple>> accountsSource,
IObservable<InlineOrderTicketProperties> propsSource,
IObservable<double[]?> calculatorButtons,
Action<SecurityMD> showLadder,
TickDisplaySettingsImmutable? quantityDisplay = null
) {
SecurityMD? currentSecurityMd = null;
DispatcherTimer? mdPollTimer = null;
var nativeBids = new NativePriceSize[numPriceLevels];
var nativeAsks = new NativePriceSize[numPriceLevels];
for (int i = 0; i < numPriceLevels; i++) {
nativeBids[i] = NativePriceSize.NO_MARKET;
nativeAsks[i] = NativePriceSize.NO_MARKET;
}
for (int i = 0; i < numPriceRowsPerSide; i++) {
priceLevels.Add(new(Side.Sell));
}
for (int i = 0; i < numPriceRowsPerSide; i++) {
priceLevels.Add(new(Side.Buy));
}
security = backing(nameof(Security), bts.api.Security.Defaults.SecurityImmutable);
title = backing(nameof(Title), security.observe_current(true), s => s.ToString() ?? string.Empty, string.Empty);
priceTick = backing(nameof(PriceTick), user.settings.RegionalTickFactors.Default);
PropertiesVm = new(model, securityMdSource, accountsSource, propsSource.Select(p => p.order_props));
this.quantityDisplay = backing(nameof(QuantityDisplay), quantityDisplay ?? bts.api.Security.Defaults.quantity_display_settingsi);
priceDisplay = backing(nameof(PriceDisplay), bts.api.Security.Defaults.price_display_settingsi);
SizeCalculator = new(0, calculatorButtons);
SendOrderCmd = new DelegateCommand(SendOrder);
SetPriceCmd = new DelegateCommand<double>(SetPrice);
ShowLadderCmd = new DelegateCommand(() => {
if (currentSecurityMd.HasValue)
showLadder(currentSecurityMd.Value);
});
RecenterCmd = new DelegateCommand(Recenter);
_dispose = Disposable.Create(
securityMdSource.OnDispatcher().Subscribe(OnSecurityMd),
SizeCalculator,
propsSource.OnDispatcher().Subscribe(p => OnProps(p.price, p.size)));
void OnProps(
InlineUserProperty<double> priceProp, InlineUserProperty<double> sizeProp) {
if (priceProp.has_value && (priceProp.force_override || DoubleHelper.IsNaNOrInfinity(Price)))
SetPrice(priceProp.value);
if (sizeProp.has_value && (sizeProp.force_override || SizeCalculator.value == 0))
SizeCalculator.value = sizeProp.value;
}
void SetPrice(double price) {
Price = price;
RaisePropertyChanged(nameof(Price));
}
void SendOrder() {
bool priceValid = validatePrice();
bool quantityValid = SizeCalculator.validate();
if (priceValid && quantityValid && !HasErrors && !PropertiesVm.HasErrors) {
model.send_order(Security, PropertiesVm.create(side), Price, SizeCalculator.value!.Value).ignore();
}
}
void Recenter() =>
RequestRecenter?.Invoke();
void OnSecurityMd(SecurityMD smd) {
currentSecurityMd = smd;
var spec = TickUtils.multiply_tick_size(smd.security.regional_ticks(), 1);
security.set(smd.security);
priceTick.set(smd.security.regional_ticks());
priceDisplay.set(smd.price_display);
this.quantityDisplay.set(smd.quantity_display);
SizeCalculator.display_settings = smd.quantity_display;
ulong lastMdPollTime = 0;
mdPollTimer?.Stop();
mdPollTimer = new DispatcherTimer(TimeSpan.FromSeconds(0.1), DispatcherPriority.DataBind, PollMd, Dispatcher.CurrentDispatcher);
void PollMd(object? sender, EventArgs e) {
if (smd.md.get_depth(ref lastMdPollTime, ref nativeBids, ref nativeAsks, numPriceLevels)) {
if (double.IsNaN(priceLevels[0].Price)) {
// Initialize prices and recenter
if (!TryGetOrigin(out var origin)) {
return;
}
double price = spec.tickdown(origin, numPriceRowsPerSide - 1);
for (int i = priceLevels.Count - 1; i >= 0; i--) {
priceLevels[i].Price = price;
price = spec.tickup(price);
}
Recenter();
}
var hi = priceLevels[0].Price;
var lo = priceLevels[^1].Price;
Log.assert(hi.is_real() && lo.is_real() && hi > lo);
var wrappedPrices = priceLevels.WrapSelector(p => p.Price, null);
var rPriceComp = spec.price_comparer().reverse();
// Clear old prices
for (int i = 0; i < numPriceLevels; i++) {
// asks
var px = nativeAsks[i].price;
if (!double.IsNaN(px) && px.is_within_range(lo, hi)) {
int idx = CollectionHelper.binary_search(wrappedPrices, px, rPriceComp);
if (idx < 0)
idx = ~idx;
priceLevels[idx].Quantity = 0;
}
// bids
px = nativeBids[i].price;
if (!double.IsNaN(px) && px.is_within_range(lo, hi)) {
int idx = CollectionHelper.binary_search(wrappedPrices, px, rPriceComp);
if (idx < 0)
idx = ~idx;
priceLevels[idx].Quantity = 0;
}
}
// Set new prices
for (int i = 0; i < numPriceLevels; i++) {
// asks
var px = nativeAsks[i].price;
if (!double.IsNaN(px) && px.is_within_range(lo, hi)) {
int idx = CollectionHelper.binary_search(wrappedPrices, px, rPriceComp);
if (idx < 0)
idx = ~idx;
priceLevels[idx].Quantity = nativeAsks[i].size;
priceLevels[idx].Side = Side.Sell;
}
// bids
px = nativeBids[i].price;
if (!double.IsNaN(px) && px.is_within_range(lo, hi)) {
int idx = CollectionHelper.binary_search(wrappedPrices, px, rPriceComp);
if (idx < 0)
idx = ~idx;
priceLevels[idx].Quantity = nativeBids[i].size;
priceLevels[idx].Side = Side.Buy;
}
}
}
}
bool TryGetOrigin(out double origin) {
if (!nativeBids[0].is_nan) {
origin = nativeBids[0].price;
} else if (!nativeAsks[0].is_nan) {
origin = nativeAsks[0].price;
} else {
origin = double.NaN;
return false;
}
return true;
}
}
}
public event Action? RequestRecenter;
public SecurityImmutable Security => security.Value;
public string Title => title.Value;
public double Price { get; set; }
public user.settings.RegionalTickFactors PriceTick => priceTick.Value;
public TickDisplaySettingsImmutable PriceDisplay => priceDisplay.Value;
public TickDisplaySettingsImmutable QuantityDisplay => quantityDisplay.Value;
public CalculatorLogic<double> SizeCalculator { get; }
public Side Side {
get => side;
set => Set(ref side, value);
}
public OrderPropertiesEditorVM PropertiesVm { get; }
public ObservableCollection<OrderTicketLadderPriceLevelVM> PriceLevels => priceLevels;
public ICommand SendOrderCmd { get; }
public ICommand SetPriceCmd { get; }
public ICommand ShowLadderCmd { get; }
public ICommand RecenterCmd { get; }
bool validatePrice() {
if (!Price.is_real()) {
flash_error(nameof(Price)).ignore();
return false;
}
return true;
}
}
class OrderTicketLadderPriceLevelVM : ViewModelBase {
Color background = Colors.Transparent;
Color priceForeground = BTSColors.Control.normal_foreground;
Side side;
double price = double.NaN;
double quantity = double.NaN;
public OrderTicketLadderPriceLevelVM(Side side) {
this.side = side;
background = BTSColors.Ladder.price_column_background;
priceForeground = BTSColors.Ladder.price_column_foreground;
}
public Side Side {
get => side;
set => Set(ref side, value, dependent_props: nameof(QuantityForeground));
}
public double Price {
get => price;
set => Set(ref price, value);
}
public double Quantity {
get => quantity;
set => Set(ref quantity, value);
}
public Color Background {
get => background;
set => Set(ref background, value);
}
public Color PriceForeground {
get => priceForeground;
set => Set(ref priceForeground, value);
}
public Color QuantityForeground =>
side switch {
Side.Buy => BTSColors.Ladder.bid_column_foreground,
Side.Sell => BTSColors.Ladder.ask_column_foreground,
_ => BTSColors.UI.default_foreground,
};
}
@@ -0,0 +1,415 @@
<UserControl
x:Class="mbts.ui.spark.OrderModificationLadderControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:api="clr-namespace:bts.api;assembly=Bts.Spark.Api"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
u:AttachedCommands.MiddleClick="{Binding RecenterCmd}" >
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Colors.xaml"/>
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/OrderDetailsPopupControl.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!-- Consts -->
<sys:Double x:Key="HorizontalSpacing">8.0</sys:Double>
<sys:Double x:Key="VerticalSpacing">8.0</sys:Double>
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
<!-- Converters -->
<cv:NullableDoubleConverter x:Key="TickDefaultPriceConverter" DefaultValue="0"/>
<cv:ValueMap x:Key="SideToBrushConverter" Default="{x:Static Brushes.Transparent}">
<cv:KeyValue Key="{u:Enum Buy, Type=api:Side}" Value="{u:BTSBrush MarketData.bid, Opacity=0.33}" />
<cv:KeyValue Key="{u:Enum Sell, Type=api:Side}" Value="{u:BTSBrush MarketData.ask, Opacity=0.33}" />
</cv:ValueMap>
<cv:ValueMap x:Key="SideToIsBuyConverter" Default="{u:False}">
<cv:KeyValue Key="{u:Enum Buy, Type=api:Side}" Value="{u:True}" />
</cv:ValueMap>
<cv:ValueMap x:Key="SideToIsSellConverter" Default="{u:False}">
<cv:KeyValue Key="{u:Enum Sell, Type=api:Side}" Value="{u:True}" />
</cv:ValueMap>
<cv:ValueMap x:Key="SideToBidVisibilityConverter" Default="{u:Visibility Collapsed}">
<cv:KeyValue Key="{u:Enum Buy, Type=api:Side}" Value="{u:Visibility Visible}" />
</cv:ValueMap>
<cv:ValueMap x:Key="SideToAskVisibilityConverter" Default="{u:Visibility Collapsed}">
<cv:KeyValue Key="{u:Enum Sell, Type=api:Side}" Value="{u:Visibility Visible}" />
</cv:ValueMap>
<!-- Styles -->
<Style TargetType="Button" BasedOn="{StaticResource DefaultButton}">
<Setter Property="Height" Value="{StaticResource ButtonHeight}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
<Style TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="Height" Value="{StaticResource ButtonHeight}"/>
<Style.Triggers>
<Trigger Property="usp:OrderModificationLadderControl.IsDirty" Value="True">
<Setter Property="BorderBrush" Value="{u:BTSColor UI.input_dirty_border}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="usp:PriceEntryControl" BasedOn="{StaticResource {x:Type usp:PriceEntryControl}}">
<Style.Triggers>
<Trigger Property="usp:OrderModificationLadderControl.IsDirty" Value="True">
<Setter Property="BorderBrush" Value="{u:BTSColor UI.input_dirty_border}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="c:TickUpDownNullableUInt32" BasedOn="{StaticResource {x:Type c:TickUpDownNullableUInt32}}">
<Style.Triggers>
<Trigger Property="usp:OrderModificationLadderControl.IsDirty" Value="True">
<Setter Property="BorderBrush" Value="{u:BTSColor UI.input_dirty_border}"/>
</Trigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="PriceLevelItemTemplate">
<Button
Background="{Binding Background, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
MinWidth="96"
Padding="4,2"
Command="{Binding DataContext.SetPriceCmd, RelativeSource={RelativeSource AncestorType=usp:OrderModificationLadderControl}}"
CommandParameter="{Binding Price}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="40" SharedSizeGroup="Orders" />
<ColumnDefinition Width="*" MinWidth="40" SharedSizeGroup="BidSize" />
<ColumnDefinition Width="Auto" MinWidth="56" SharedSizeGroup="Price" />
<ColumnDefinition Width="*" MinWidth="40" SharedSizeGroup="AskSize" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Right"
Foreground="{Binding QuantityForeground, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
FontSize="{x:Static u:FontSizes.Small}"
Visibility="{Binding Side, Converter={StaticResource SideToBidVisibilityConverter}}">
<TextBlock.Text>
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
<Binding Path="Quantity" Mode="OneWay"/>
<Binding Path="DataContext.QuantityDisplay" RelativeSource="{RelativeSource AncestorType=usp:OrderModificationLadderControl}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock
Grid.Column="2"
HorizontalAlignment="Center"
Foreground="{Binding PriceForeground, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
FontSize="{x:Static u:FontSizes.Small}">
<TextBlock.Text>
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
<Binding Path="Price" Mode="OneWay"/>
<Binding Path="DataContext.PriceDisplay" RelativeSource="{RelativeSource AncestorType=usp:OrderModificationLadderControl}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock
Grid.Column="3"
HorizontalAlignment="Left"
Foreground="{Binding QuantityForeground, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
FontSize="{x:Static u:FontSizes.Small}"
Visibility="{Binding Side, Converter={StaticResource SideToAskVisibilityConverter}}">
<TextBlock.Text>
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
<Binding Path="Quantity" Mode="OneWay"/>
<Binding Path="DataContext.QuantityDisplay" RelativeSource="{RelativeSource AncestorType=usp:OrderModificationLadderControl}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</Button>
</DataTemplate>
<ControlTemplate x:Key="BuyRadioButtonTemplate" TargetType="RadioButton">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="hoverOverlay" Storyboard.TargetProperty="Opacity" To="0.3" BeginTime="0" Duration="0" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed" />
<VisualState x:Name="Disabled">
<Storyboard>
<ColorAnimation Storyboard.TargetName="background" Storyboard.TargetProperty="Background.Color" To="#555" Duration="0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<ColorAnimation Storyboard.TargetName="background" Storyboard.TargetProperty="Background.Color" To="{u:BTSColor MarketData.bid}" BeginTime="0" Duration="0"/>
<ColorAnimation Storyboard.TargetName="background" Storyboard.TargetProperty="BorderBrush.Color" To="{u:BTSColor MarketData.bid}" BeginTime="0" Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked" />
<VisualState x:Name="Indeterminate" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border
x:Name="background"
BorderThickness="{TemplateBinding BorderThickness}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Padding="{TemplateBinding Padding}"
CornerRadius="2">
<Border.Background>
<SolidColorBrush Color="Transparent" />
</Border.Background>
<Border.BorderBrush>
<SolidColorBrush Color="Black"/>
</Border.BorderBrush>
</Border>
<ContentPresenter
Name="presenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding HorizontalContentAlignment}" />
<Border Name="hoverOverlay" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Black" Opacity="0" IsHitTestVisible="False" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="SellRadioButtonTemplate" TargetType="RadioButton">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="hoverOverlay" Storyboard.TargetProperty="Opacity" To="0.3" BeginTime="0" Duration="0" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed" />
<VisualState x:Name="Disabled">
<Storyboard>
<ColorAnimation Storyboard.TargetName="background" Storyboard.TargetProperty="Background.Color" To="#555" Duration="0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<ColorAnimation Storyboard.TargetName="background" Storyboard.TargetProperty="Background.Color" To="{u:BTSColor MarketData.ask}" BeginTime="0" Duration="0"/>
<ColorAnimation Storyboard.TargetName="background" Storyboard.TargetProperty="BorderBrush.Color" To="{u:BTSColor MarketData.ask}" BeginTime="0" Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked" />
<VisualState x:Name="Indeterminate" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border
x:Name="background"
BorderThickness="{TemplateBinding BorderThickness}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Padding="{TemplateBinding Padding}"
CornerRadius="2">
<Border.Background>
<SolidColorBrush Color="Transparent" />
</Border.Background>
<Border.BorderBrush>
<SolidColorBrush Color="Black"/>
</Border.BorderBrush>
</Border>
<ContentPresenter
Name="presenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
<Border Name="hoverOverlay" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Black" Opacity="0" IsHitTestVisible="False" />
</Grid>
</ControlTemplate>
<Style x:Key="BuyRadioButtonStyle" TargetType="RadioButton">
<Setter Property="Padding" Value="2" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Foreground" Value="{u:BTSBrush UI.default_text}" />
<Setter Property="Template" Value="{StaticResource BuyRadioButtonTemplate}" />
</Style>
<Style x:Key="SellRadioButtonStyle" TargetType="RadioButton">
<Setter Property="Padding" Value="2" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Foreground" Value="{u:BTSBrush UI.default_text}" />
<Setter Property="Template" Value="{StaticResource SellRadioButtonTemplate}" />
</Style>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- Ladder (market depth) -->
<Grid Grid.Column="2" Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid
Grid.Row="0"
Background="{u:BTSBrush header}"
TextElement.Foreground="{u:BTSBrush header_text}"
TextElement.FontSize="{x:Static u:FontSizes.Small}"
TextElement.FontWeight="SemiBold">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="40" SharedSizeGroup="Orders" />
<ColumnDefinition Width="*" MinWidth="40" SharedSizeGroup="BidSize" />
<ColumnDefinition Width="Auto" MinWidth="56" SharedSizeGroup="Price" />
<ColumnDefinition Width="*" MinWidth="40" SharedSizeGroup="AskSize" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" HorizontalAlignment="Center" Text="Orders" />
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Text="Bid" />
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Text="Price" />
<TextBlock Grid.Column="3" HorizontalAlignment="Center" Text="Ask" />
</Grid>
<ScrollViewer Grid.Row="1" x:Name="priceLevelsScrollViewer" MaxHeight="240">
<ItemsControl ItemsSource="{Binding PriceLevels}" ItemTemplate="{StaticResource PriceLevelItemTemplate}" />
</ScrollViewer>
</Grid>
<!-- Order Inputs -->
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}" />
<RowDefinition Height="*" />
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Buy/Sell toggle -->
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" MinWidth="72" />
<ColumnDefinition Width="1*" MinWidth="72" />
</Grid.ColumnDefinitions>
<RadioButton
Grid.Column="0"
GroupName="Side"
IsChecked="{Binding Side, Converter={StaticResource SideToIsBuyConverter}}"
Style="{StaticResource BuyRadioButtonStyle}">
<TextBlock FontWeight="Bold" Foreground="{u:BTSBrush UI.default_text}">
Buy
</TextBlock>
</RadioButton>
<RadioButton
Grid.Column="1"
GroupName="Side"
IsChecked="{Binding Side, Converter={StaticResource SideToIsSellConverter}}"
Style="{StaticResource SellRadioButtonStyle}">
<TextBlock FontWeight="Bold" Foreground="{u:BTSBrush UI.default_text}">
Sell
</TextBlock>
</RadioButton>
</Grid>
<usp:PriceEntryControl
Grid.Row="2"
usp:OrderModificationControl.IsDirty="{Binding PriceDirty, FallbackValue={x:Null}}"
RegionalTick="{Binding PriceTick}"
Focusable="False"
TickDefaultValue="{Binding CurrentPrice, Converter={StaticResource TickDefaultPriceConverter}, FallbackValue={u:Double 0}}"
ToolTip="Price"
Value="{Binding Price}" />
<c:TickUpDownNullableDouble
Grid.Row="4"
usp:OrderModificationControl.IsDirty="{Binding QuantityDirty, FallbackValue={x:Null}}"
CommitOnEnter="True"
Focusable="False"
Increment="{Binding SizeCalculator.unit_quantity}"
Minimum="0"
TickDisplaySettings="{Binding SizeCalculator.display_settings}"
ToolTip="Quantity"
Value="{Binding SizeCalculator.display_value}" />
<uag:OrderDetailsPopupControl
Grid.Row="6"
DataContext="{Binding PropertiesVm}"
usp:OrderModificationControl.IsDirty="{Binding DataContext.order_type_dirty, RelativeSource={RelativeSource AncestorType=usp:OrderModificationControl}, FallbackValue={u:False}}"
HasEditableProperties="{Binding can_expand, FallbackValue={u:False}}"
IsAlgoTemplateToolBarReadOnly="True"
IsEnabled="{Binding DataContext.can_change_order_type, RelativeSource={RelativeSource AncestorType=usp:OrderModificationControl}, FallbackValue={u:True}}"
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>
<Trigger Property="usp:OrderModificationControl.IsDirty" Value="True">
<Setter Property="BorderBrush" Value="{u:BTSColor UI.input_dirty_border}"/>
</Trigger>
</Style.Triggers>
</Style>
</uag:OrderDetailsPopupControl.Style>
</uag:OrderDetailsPopupControl>
<ComboBox
Grid.Row="8"
usp:OrderModificationControl.IsDirty="{Binding AccountDirty, FallbackValue={u:False}}"
IsEnabled="{Binding CanChangeAccount, FallbackValue={u:True}}"
ItemsSource="{Binding PropertiesVm.accounts}"
SelectedItem="{Binding PropertiesVm.selected_account}"
ToolTip="Account" />
<ComboBox
Grid.Row="10"
usp:OrderModificationControl.IsDirty="{Binding TimeInForceDirty, FallbackValue={u:False}}"
IsEnabled="{Binding CanChangeTimeInForce, FallbackValue={u:True}}"
ItemsSource="{Binding PropertiesVm.time_in_forces}"
SelectedItem="{Binding PropertiesVm.selected_time_in_force}"
ToolTip="Time In Force" />
<Button
Grid.Row="12"
Height="NaN"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="Green"
Foreground="{u:BTSBrush UI.default_text}"
FontSize="{x:Static u:FontSizes.Largest}"
FontWeight="Bold"
Command="{Binding SendOrderCmd}"
ToolTip="{Binding Side, StringFormat='Send {0} Order'}">
Send
</Button>
<TextBlock
Grid.Row="14"
HorizontalAlignment="Center"
FontSize="{x:Static u:FontSizes.Small}">
<Hyperlink
Command="{Binding ShowLadderCmd}"
ToolTip="Open the original ladder for this security">Open full ladder</Hyperlink>
</TextBlock>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,51 @@
#nullable enable
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace mbts.ui.spark {
interface IOrderModificationLadderVM {
event Action? RequestRecenter;
}
/// <summary>
/// Interaction logic for OrderModificationLadderControl.xaml
/// </summary>
public partial class OrderModificationLadderControl : UserControl {
public static readonly DependencyProperty IsDirtyProperty = DependencyProperty.RegisterAttached(
"IsDirty", typeof(bool?), typeof(OrderModificationLadderControl), new PropertyMetadata(null));
public static bool? GetIsDirty(DependencyObject d) => d.GetValue(IsDirtyProperty) as bool?;
public static void SetIsDirty(DependencyObject d, bool? value) => d.SetValue(IsDirtyProperty, value);
public OrderModificationLadderControl() {
InitializeComponent();
DataContextChanged += onDataContextChanged;
}
void onDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {
if (e.OldValue is IOrderModificationLadderVM oldVm) {
oldVm.RequestRecenter -= recenter;
}
if (e.NewValue is IOrderModificationLadderVM newVm) {
newVm.RequestRecenter += recenter;
}
}
void recenter() {
var targetOffset = priceLevelsScrollViewer.ScrollableHeight / 2;
DoubleAnimation anim = new() {
From = priceLevelsScrollViewer.VerticalOffset,
To = targetOffset,
Duration = TimeSpan.FromSeconds(0.15)
};
Storyboard sb = new();
sb.Children.Add(anim);
Storyboard.SetTarget(anim, priceLevelsScrollViewer);
Storyboard.SetTargetProperty(anim, new PropertyPath(Behavior.AnimatedScrollViewerVerticalOffsetProperty));
sb.Begin();
}
}
}
@@ -0,0 +1,120 @@
<Window
x:Class="mbts.ui.spark.OrderTicketLadder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:msys="clr-namespace:bts.api;assembly=Bts.Spark.Api"
xmlns:u="clr-namespace:mbts.ui"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
u:WindowStyle.SizeToContent="WidthAndHeight"
u:WindowStyle.TitleContent="{Binding Title, Mode=OneWay}"
BorderBrush="#00C000"
BorderThickness="1"
Style="{StaticResource BTSWindowTitleTemplate}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="HorizontalSpacing">8.0</sys:Double>
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
<Style x:Key="SellButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.ask_dark}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Text="Sell" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="1"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{u:BTSColor MarketData.ask}"
Padding="2"
Height="Auto"
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="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="{u:BTSColor MarketData.bid_dark}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Content">
<Setter.Value>
<TextBlock VerticalAlignment="Center" Text="Buy" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Border"
CornerRadius="0"
BorderThickness="1"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{u:BTSColor MarketData.bid}"
Padding="2"
Height="Auto"
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>
</ResourceDictionary>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="Esc" Command="{x:Static SystemCommands.CloseWindowCommand}"/>
</Window.InputBindings>
<usp:OrderModificationLadderControl Margin="10,4" />
<u:WindowStyle.TitleContentTemplate>
<DataTemplate>
<TextBlock HorizontalAlignment="Center" FontWeight="Bold" Text="{Binding}" Style="{StaticResource TextBlock.Dark}" />
</DataTemplate>
</u:WindowStyle.TitleContentTemplate>
</Window>
@@ -0,0 +1,15 @@
using mbts.ui.utility;
using System.Windows;
namespace mbts.ui.spark {
/// <summary>
/// Interaction logic for OrderTicketLadder.xaml
/// </summary>
public partial class OrderTicketLadder : Window {
public OrderTicketLadder() {
InitializeComponent();
WindowUtilities.listen_for_close_requests(this);
}
}
}
@@ -1,117 +1,119 @@
#nullable enable
namespace mbts.ui {
using bts.utils;
using Spark.Core.Reflection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
[MarkupExtensionReturnType]
class BTSColorExtension : MarkupExtension {
static readonly Dictionary<string, Func<BTSColor>>
_color_map = _normalized_color_props(typeof(BTSColors), null).ToDictionary();
public BTSColorExtension() { }
public BTSColorExtension(string? name) =>
Name = name;
public BTSColorExtension(string? name, string? mixture) {
Name = name;
Mixture = mixture;
}
[ConstructorArgument("name")]
public string? Name { get; set; }
[ConstructorArgument("mixture")]
public string? Mixture { get; set; }
public ColorMixtureMode MixtureMode { get; set; } = ColorMixtureMode.Default;
public Type? Type { get; set; }
public double Opacity { get; set; } = double.NaN;
public static BTSColor get_color(string name) =>
_color_map.TryGetValue(name, out var color) ?
color() : throw new ColorNotFoundException(name);
public override object? ProvideValue(IServiceProvider service_provider) {
if (service_provider is null) {
return null;
}
if (Name is null) {
throw new InvalidOperationException("Name is null");
}
if (!Opacity.is_nan() && !Opacity.is_within_range(0, 1)) {
throw new InvalidOperationException("Opacity (pct) must between 0 and 1 (if not NaN)");
}
if (Name.Contains("Launcher.Button")) {
}
var pvt = service_provider.GetValueTargetProvider() ??
throw new InvalidOperationException("IProvideValueTarget is null");
var tgt_type = Type ?? _deduce_target_type(pvt);
var names = Name.Split(',');
var mixtures = Mixture?.Split(',');
var color = _get_color_blend(names, mixtures, MixtureMode);
if (!Opacity.is_nan()) {
color = color.with((byte)(Opacity * byte.MaxValue));
}
return tgt_type == typeof(Color) ? (Color)color : (Brush)color;
}
static Type _deduce_target_type(IProvideValueTarget pvt) {
if (pvt.TargetProperty is null) {
return typeof(Color);
}
if (pvt.TargetProperty is DependencyProperty dp) {
return dp.PropertyType ?? throw new InvalidOperationException();
}
if (pvt.TargetObject is Setter setter) {
return setter.Property?.PropertyType ?? typeof(Brush);
}
return typeof(Brush);
}
/// <summary>
/// Creates a (potentially weighted) blend of colors
/// </summary>
static BTSColor _get_color_blend(string[] names, string[]? mixture, ColorMixtureMode mode) {
if (mixture is null || mixture.Length == 0) {
// create a uniform blend
return names.Select(get_color).blend(mode);
}
if (names.Length != mixture.Length) {
throw new InvalidOperationException(
"Names and Mixture must provide same number of elements");
}
return names.Select(get_color).Zip(mixture.Select(double.Parse)).blend();
}
static IEnumerable<(string, Func<BTSColor>)> _normalized_color_props(
Type type, string? prefix) {
var bts_colors = ReflectionHelper.static_property_getters<BTSColor>(type)
.Select(tup => (full_name(tup.name), tup.get));
var media_colors = ReflectionHelper.static_property_getters<Color>(type)
.Select(tup => (full_name(tup.name), new Func<BTSColor>(() => tup.get())));
var nested_types = type.GetNestedTypes(BindingFlags.Public | BindingFlags.Static);
var nested_colors = nested_types.SelectMany(
t => _normalized_color_props(t, full_name(t.Name))).ToList();
return bts_colors
.Concat(media_colors)
.Concat(nested_colors);
string full_name(string prop) =>
prefix is null ? prop : $"{prefix}.{prop}";
}
}
}
#nullable enable
namespace mbts.ui {
using bts.utils;
using Spark.Core.Reflection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
[MarkupExtensionReturnType]
class BTSColorExtension : MarkupExtension {
static readonly Dictionary<string, Func<BTSColor>>
_color_map = _normalized_color_props(typeof(BTSColors), null).ToDictionary();
public BTSColorExtension() { }
public BTSColorExtension(string? name) =>
Name = name;
public BTSColorExtension(string? name, string? mixture) {
Name = name;
Mixture = mixture;
}
[ConstructorArgument("name")]
public string? Name { get; set; }
[ConstructorArgument("mixture")]
public string? Mixture { get; set; }
public ColorMixtureMode MixtureMode { get; set; } = ColorMixtureMode.Default;
public Type? Type { get; set; }
public double Opacity { get; set; } = double.NaN;
public static BTSColor get_color(string name) =>
_color_map.TryGetValue(name, out var color) ?
color() : throw new ColorNotFoundException(name);
public override object? ProvideValue(IServiceProvider service_provider) {
if (service_provider is null) {
return null;
}
if (Name is null) {
throw new InvalidOperationException("Name is null");
}
if (!Opacity.is_nan() && !Opacity.is_within_range(0, 1)) {
throw new InvalidOperationException("Opacity (pct) must between 0 and 1 (if not NaN)");
}
if (Name.Contains("Launcher.Button")) {
}
var pvt = service_provider.GetValueTargetProvider() ??
throw new InvalidOperationException("IProvideValueTarget is null");
var tgt_type = Type ?? _deduce_target_type(pvt);
var names = Name.Split(',');
var mixtures = Mixture?.Split(',');
var color = _get_color_blend(names, mixtures, MixtureMode);
if (!Opacity.is_nan()) {
color = color.with((byte)(Opacity * byte.MaxValue));
}
return tgt_type == typeof(Color) || tgt_type == typeof(Color?)
? (Color)color
: (Brush)color;
}
static Type _deduce_target_type(IProvideValueTarget pvt) {
if (pvt.TargetProperty is null) {
return typeof(Color);
}
if (pvt.TargetProperty is DependencyProperty dp) {
return dp.PropertyType ?? throw new InvalidOperationException();
}
if (pvt.TargetObject is Setter setter) {
return setter.Property?.PropertyType ?? typeof(Brush);
}
return typeof(Brush);
}
/// <summary>
/// Creates a (potentially weighted) blend of colors
/// </summary>
static BTSColor _get_color_blend(string[] names, string[]? mixture, ColorMixtureMode mode) {
if (mixture is null || mixture.Length == 0) {
// create a uniform blend
return names.Select(get_color).blend(mode);
}
if (names.Length != mixture.Length) {
throw new InvalidOperationException(
"Names and Mixture must provide same number of elements");
}
return names.Select(get_color).Zip(mixture.Select(double.Parse)).blend();
}
static IEnumerable<(string, Func<BTSColor>)> _normalized_color_props(
Type type, string? prefix) {
var bts_colors = ReflectionHelper.static_property_getters<BTSColor>(type)
.Select(tup => (full_name(tup.name), tup.get));
var media_colors = ReflectionHelper.static_property_getters<Color>(type)
.Select(tup => (full_name(tup.name), new Func<BTSColor>(() => tup.get())));
var nested_types = type.GetNestedTypes(BindingFlags.Public | BindingFlags.Static);
var nested_colors = nested_types.SelectMany(
t => _normalized_color_props(t, full_name(t.Name))).ToList();
return bts_colors
.Concat(media_colors)
.Concat(nested_colors);
string full_name(string prop) =>
prefix is null ? prop : $"{prefix}.{prop}";
}
}
}
@@ -380,7 +380,7 @@
<VisualState Name="Normal"/>
<VisualState Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{StaticResource u:BTSColor UI.ScrollBar.thumb_mouse_over}"/>
<ColorAnimation Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="{u:BTSColor UI.ScrollBar.thumb_mouse_over}"/>
</Storyboard>
</VisualState>
<VisualState Name="Pressed">
@@ -1,5 +1,5 @@
<ResourceDictionary
x:Class="mbts.ui.spark.AuxiliaryDataResources"
x:Class="mbts.ui.spark.OrderAuxiliaryPopupResources"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -17,7 +17,7 @@
<sp:MiniOrderBookView/>
</DataTemplate>
<!--Header (Title Bar)-->
<DataTemplate DataType="{x:Type usp:AuxiliaryDataHeader}">
<DataTemplate DataType="{x:Type usp:OrderAuxiliaryPopupHeader}">
<Grid MaxWidth="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=ContentPresenter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -101,6 +101,20 @@
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type usp:OrderTicketLadderHeader}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock FontWeight="Bold" Text="{Binding Title}" Style="{StaticResource TextBlock.Dark}" />
<CheckBox Grid.Column="2" VerticalAlignment="Center" Margin="4,0,0,0" Focusable="False" IsChecked="{Binding StaysOpen, RelativeSource={RelativeSource AncestorType=Popup}}">Keep Open</CheckBox>
<Button Grid.Column="3" HorizontalAlignment="Right" Margin="4" Command="{x:Static ApplicationCommands.Close}" CommandTarget="{Binding RelativeSource={RelativeSource AncestorType=HeaderedContentControl}}" Style="{StaticResource RoundCloseButton}"/>
</Grid>
</DataTemplate>
<ControlTemplate x:Key="PopupChildTemplate" TargetType="HeaderedContentControl">
<Border
Background="{TemplateBinding Background}"
@@ -0,0 +1,49 @@
#nullable enable
using mbts.spark;
using mbts.ui.shell;
using System.Windows;
using System.Windows.Media;
namespace mbts.ui.spark {
partial class OrderAuxiliaryPopupResources : ResourceDictionary {
public OrderAuxiliaryPopupResources() =>
InitializeComponent();
public static string PopupChildStyleKey => nameof(PopupChildStyleKey);
}
class OrderAuxiliaryPopupHeader : ViewModelBase {
string?
_title,
_context;
Color _title_foreground;
bool _keep_open;
public Color title_foreground { get => _title_foreground; set => Set(ref _title_foreground, value); }
public string? title { get => _title; set => Set(ref _title, value); }
public string? context { get => _context; set => Set(ref _context, value); }
public bool keep_open { get => _keep_open; set => Set(ref _keep_open, value); }
}
class OrderTicketHeader : ViewModelBase {
bool _keep_open;
public BBOVM? bbo { get; set; }
public bool keep_open { get => _keep_open; set => Set(ref _keep_open, value); }
}
class OrderTicketLadderHeader : ViewModelBase {
bool keepOpen;
string? title;
public string? Title {
get => title;
set => Set(ref title, value);
}
public bool KeepOpen {
get => keepOpen;
set => Set(ref keepOpen, value);
}
}
}
@@ -102,6 +102,10 @@
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type sp:NewOrderTicketLadderVM}">
<usp:OrderModificationLadderControl />
</DataTemplate>
<DataTemplate DataType="{x:Type sp:ExistingOrderTicketVM}">
<Grid Margin="10">
<Grid.RowDefinitions>
@@ -1,36 +0,0 @@
using mbts.spark;
using mbts.ui.shell;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace mbts.ui.spark {
partial class AuxiliaryDataResources : ResourceDictionary {
public AuxiliaryDataResources() =>
InitializeComponent();
public static string PopupChildStyleKey => nameof(PopupChildStyleKey);
}
class AuxiliaryDataHeader : ViewModelBase {
string
_title,
_context;
Color _title_foreground;
bool _keep_open;
public Color title_foreground { get => _title_foreground; set => Set(ref _title_foreground, value); }
public string title { get => _title; set => Set(ref _title, value); }
public string context { get => _context; set => Set(ref _context, value); }
public bool keep_open { get => _keep_open; set => Set(ref _keep_open, value); }
}
class OrderTicketHeader : ViewModelBase {
bool _keep_open;
public BBOVM bbo { get; set; }
public bool keep_open { get => _keep_open; set => Set(ref _keep_open, value); }
}
class ConstrainedTextBlock : TextBlock {
}
}
@@ -26,6 +26,39 @@ namespace mbts.ui {
public static void SetDoubleClickParameter(DependencyObject target, object value) { target.SetValue(DoubleClickParameterProperty, value); }
#endregion
#region MiddleClick
public static readonly DependencyProperty MiddleClickProperty =
DependencyProperty.RegisterAttached("MiddleClick", typeof(ICommand), typeof(AttachedCommands), new PropertyMetadata(onMiddleClickChanged));
static void onMiddleClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var el = (UIElement)d;
el.ClearMouseBindings(MouseAction.MiddleClick);
if (e.NewValue is ICommand cmd) {
var mb = new MouseBinding(cmd, new MouseGesture(MouseAction.MiddleClick));
var bd = new Binding { Path = new PropertyPath(MiddleClickParameterProperty), Source = d };
BindingOperations.SetBinding(mb, InputBinding.CommandParameterProperty, bd);
el.InputBindings.Add(mb);
}
}
public static ICommand GetMiddleClick(DependencyObject target) =>
target.GetValue(MiddleClickProperty) as ICommand;
public static void SetMiddleClick(DependencyObject target, ICommand value) =>
target.SetValue(MiddleClickProperty, value);
#endregion
#region MiddleClickParameter
public static readonly DependencyProperty MiddleClickParameterProperty =
DependencyProperty.RegisterAttached("MiddleClickParameter", typeof(object), typeof(AttachedCommands));
public static object GetMiddleClickParameter(DependencyObject target) =>
target.GetValue(MiddleClickParameterProperty);
public static void SetMiddleClickParameter(DependencyObject target, object value) =>
target.SetValue(MiddleClickParameterProperty, value);
#endregion
#region Enter
public static readonly DependencyProperty EnterProperty = DependencyProperty.RegisterAttached("Enter", typeof(ICommand), typeof(AttachedCommands), new PropertyMetadata(OnEnterChanged));
static void OnEnterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+21 -2
View File
@@ -11,6 +11,7 @@ namespace Spark.Api.Orders {
using Spark.Api.Admin;
using Spark.Api.MarketData;
using Spark.Api.Position;
using Spark.Api.Reactive;
using Spark.Core.Disposables;
using Spark.Core.Reactive;
@@ -40,10 +41,12 @@ namespace Spark.Api.Orders {
) {
_order_service = order_service;
var scheduler = ThreadPoolScheduler.Instance;
interval ??= TimeSpan.FromSeconds(0.1);
if(interval is not null)
Log.Api.info($"originally OrdersClient polling interval {interval}");
interval = TimeSpan.FromMilliseconds(1);
Log.Api.info($"OrdersClient polling interval {interval}");
var bootstraps = new DisposableBuilder();
positions = position_service.subscribei_position_data().Table().Cache();
_orders = bootstraps.emplace(
admin_client
.permission_filter(
@@ -73,6 +76,11 @@ namespace Spark.Api.Orders {
.Cache()
.ToObservable());
var raw_fills = admin_client.permission_filter(
order_service.subscribei_fills().Naggle(interval.Value, scheduler),
f => f.Account)
.Table();
var fill_order_tuples_join = fills.LeftJoin(
_orders,
f => f.val.FillKey,
@@ -94,6 +102,12 @@ namespace Spark.Api.Orders {
.Cache()
.ToObservable());
var raw_manual_fills = admin_client
.permission_filter(
position_service.subscribei_manual_fills().Naggle(interval.Value, scheduler),
f => f.AccountId)
.Table();
var manual_fill_tuples_account_join = manual_fills_securities.LeftJoin(
admin_client.Accounts,
mf => mf.val.RecordId,
@@ -117,6 +131,11 @@ namespace Spark.Api.Orders {
.Cache()
.ToObservable());
positions = bootstraps.emplace(
ComputedPositionsDataSource.create(raw_fills, raw_manual_fills)
.Cache()
.ToObservable());
_dispose = bootstraps;
}
public async Task<Response> EnterOrderAsync(
@@ -0,0 +1,194 @@
#nullable enable
namespace Spark.Api.Position {
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.api.position;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
public static class ComputedPositionsDataSource {
public static IObservable<TableUpdate<PositionUnitImmutable, int>> create(
IObservable<TableUpdate<FillImmutable, string>> fills,
IObservable<TableUpdate<ManualFillImmutable, int>> manual_fills) {
var record_ids = new Dictionary<AccountSecurityKey, int>();
int next_record_id = 1;
return Observable.CombineLatest(
fills,
manual_fills,
(fill_update, manual_fill_update) => build_snapshot(
fill_update.Index.Values,
manual_fill_update.Index.Values)
.ToDictionary(PositionUnitImmutable.key_fn))
.Table(PositionUnitImmutable.key_fn);
IEnumerable<PositionUnitImmutable> build_snapshot(
IEnumerable<FillImmutable> electronic_fills,
IEnumerable<ManualFillImmutable> manual_fill_rows) {
var positions = new SortedDictionary<AccountSecurityKey, PositionAccumulator>();
foreach (var fill in electronic_fills) {
if (fill.FillStatus == Fill.Types.FillStatus.Busted ||
fill.SecurityKey == 0 ||
fill.InternalAccountId == 0) {
continue;
}
var key = new AccountSecurityKey(fill.InternalAccountId, fill.SecurityKey);
var acc = get_or_create(positions, key, fill.Account);
acc.add_electronic_fill(fill);
positions[key] = acc;
}
foreach (var fill in manual_fill_rows) {
if (fill.SecurityKey == 0 || fill.AccountId == 0) {
continue;
}
var key = new AccountSecurityKey(fill.AccountId, fill.SecurityKey);
var acc = get_or_create(positions, key, fill.AccountName);
acc.add_manual_fill(fill);
positions[key] = acc;
}
foreach (var (key, acc) in positions) {
if (!record_ids.TryGetValue(key, out var record_id)) {
record_id = next_record_id++;
record_ids[key] = record_id;
}
yield return acc.to_position(record_id);
}
}
static PositionAccumulator get_or_create(
IDictionary<AccountSecurityKey, PositionAccumulator> positions,
AccountSecurityKey key,
string account_name) {
if (!positions.TryGetValue(key, out var acc)) {
return new PositionAccumulator(key.account_id, key.security_id, account_name);
} else if (string.IsNullOrEmpty(acc.account) && !string.IsNullOrEmpty(account_name)) {
acc.account = account_name;
}
return acc;
}
}
struct PositionAccumulator {
public PositionAccumulator(uint account_id, uint security_id, string account) {
this.account_id = account_id;
this.security_id = security_id;
this.account = account;
day = default;
sod = default;
local = default;
admin_day = default;
admin_sod = default;
raw_day = default;
}
public readonly uint account_id;
public readonly uint security_id;
public string account;
PositionEntryAccumulator day;
PositionEntryAccumulator sod;
PositionEntryAccumulator local;
PositionEntryAccumulator admin_day;
PositionEntryAccumulator admin_sod;
PositionEntryAccumulator raw_day;
public void add_electronic_fill(FillImmutable fill) {
var qty = fill.Side == Side.Sell ? -fill.LastShares : fill.LastShares;
if (is_sod(fill.FillKind)) {
sod.add(qty, fill.LastPrice);
return;
}
day.add(qty, fill.LastPrice);
raw_day.add(qty, fill.LastPrice);
}
public void add_manual_fill(ManualFillImmutable fill) {
var qty = fill.Side == Side.Sell ? -fill.Quantity : fill.Quantity;
switch (fill.FillType) {
case ManualFill.Types.FillType.AdminSod:
sod.add(qty, fill.Price);
admin_sod.add(qty, fill.Price);
break;
case ManualFill.Types.FillType.AdminDay:
day.add(qty, fill.Price);
raw_day.add(qty, fill.Price);
admin_day.add(qty, fill.Price);
break;
default:
day.add(qty, fill.Price);
raw_day.add(qty, fill.Price);
local.add(qty, fill.Price);
break;
}
}
public PositionUnitImmutable to_position(int record_id) {
var sod_entry = sod.to_entry();
var day_entry = day.to_entry();
var raw_day_entry = raw_day.to_entry();
var local_entry = local.to_entry();
var admin_sod_entry = admin_sod.to_entry();
var admin_day_entry = admin_day.to_entry();
return new PositionUnitImmutable(new PositionUnit {
RecordId = record_id,
Account = account,
AccountId = account_id,
SecurityId = security_id,
SodLongPos = sod_entry.LongPos,
SodLongAvgPrice = sod_entry.LongAvgPrice,
SodShortPos = sod_entry.ShortPos,
SodShortAvgPrice = sod_entry.ShortAvgPrice,
DayLongPos = day_entry.LongPos,
DayLongAvgPrice = day_entry.LongAvgPrice,
DayShortPos = day_entry.ShortPos,
DayShortAvgPrice = day_entry.ShortAvgPrice,
Sod = sod_entry,
Day = day_entry,
RawDay = raw_day_entry,
Local = local_entry,
AdminSod = admin_sod_entry,
AdminDay = admin_day_entry
});
}
static bool is_sod(Fill.Types.FillKind kind) =>
kind == Fill.Types.FillKind.Sod ||
kind == Fill.Types.FillKind.SodSpreadLeg ||
kind == Fill.Types.FillKind.SodStripLeg;
}
struct PositionEntryAccumulator {
double long_qty;
double long_cost;
double short_qty;
double short_cost;
public void add(double signed_qty, double price) {
if (signed_qty > 0) {
long_qty += signed_qty;
long_cost += signed_qty * price;
} else if (signed_qty < 0) {
var abs_qty = Math.Abs(signed_qty);
short_qty += abs_qty;
short_cost += abs_qty * price;
}
}
public PositionEntry to_entry() =>
new() {
LongPos = long_qty,
LongAvgPrice = long_qty == 0 ? 0 : long_cost / long_qty,
ShortPos = short_qty,
ShortAvgPrice = short_qty == 0 ? 0 : short_cost / short_qty
};
}
}
}
+15 -6
View File
@@ -15,6 +15,7 @@ namespace Spark.Trading {
using Spark.Api.Data;
using Spark.Api.MarketData;
using Spark.Api.Orders;
using Spark.Api.Position;
using Spark.Api.Reactive;
using Spark.Core;
using Spark.Core.Data;
@@ -60,13 +61,21 @@ namespace Spark.Trading {
.Cache()
.ToObservable());
var raw_fills = admin_model
.permission_filter(
order_service.subscribei_fills().Naggle(interval, scheduler),
f => f.Account)
.Table();
var raw_manual_fills = admin_model
.permission_filter(
_position_service.subscribei_manual_fills().Naggle(interval, scheduler),
f => f.AccountId)
.Table();
on_positions = bootstraps.emplace(
admin_model
.permission_filter(
_position_service.subscribei_position_data().Naggle(interval, scheduler),
p => p.AccountId)
.join(sec_resolver, pos => pos.SecurityId)
.Table()
ComputedPositionsDataSource.create(raw_fills, raw_manual_fills)
.join_table(sec_resolver, pos => pos.SecurityId)
.Cache()
.ToObservable());
var users = admin_model.Users
@@ -8,6 +8,7 @@ using bts.api.core;
using bts.api.orderman;
using bts.api.position;
using bts.co2;
using bts.metrics;
using bts.md;
using Microsoft.Extensions.Configuration;
@@ -86,6 +87,7 @@ public static class SparkApiServices {
services.AddSparkdService<OrderService>();
services.AddSingleton<IOrdersClient, OrdersClient>();
}
services.AddSparkdService<ShellLatencyService>();
if (configurationSection.GetValue("EnablePosition", true)) {
services.TryAddSingleton<ISecurityResolver>(ActivatorUtilities.GetServiceOrCreateInstance<PassiveSecurityResolver>);
services.AddSparkdService<PositionService>()
@@ -176,6 +178,7 @@ public static class SparkApiServices {
services.AddSparkdService<OrderService>();
services.AddSingleton<IOrdersClient, OrdersClient>();
}
services.AddSparkdService<ShellLatencyService>();
if (isPositionsEnabled) {
services.AddSparkdService<PositionService>();
}
@@ -3,6 +3,7 @@ using bts.api;
using bts.data.ticks;
using bts.utils;
using mbts.co2;
using mbts.user.settings;
using Spark.Core;
@@ -113,6 +114,24 @@ namespace mbts.spark {
return rv;
}
public static double low_tick(NativePriceSize[] bids, NativePriceSize[] asks) {
double low = bids[bids.Length - 1].price;
if (double.IsNaN(low))
low = bids.LastOrDefault(nps => nps.price, p => !double.IsNaN(p), double.NaN);
if (double.IsNaN(low))
low = asks[0].price;
return low;
}
public static double high_tick(NativePriceSize[] bids, NativePriceSize[] asks) {
double high = asks[asks.Length - 1].price;
if (double.IsNaN(high))
high = asks.LastOrDefault(nps => nps.price, p => !double.IsNaN(p), double.NaN);
if (double.IsNaN(high))
high = bids[0].price;
return high;
}
public static long? tickdiff(this ContractSpecificationImmutable spec, double x, double y) =>
get_regional_ticks(spec).tickdiff(x, y);
+6 -5
View File
@@ -41,8 +41,8 @@ class TopOfBookQuoter : IHostedService {
throw new ArgumentException("Failed to parse venue", nameof(config));
}
var quantity = config.Quantity;
if (!(quantity > 0)) {
throw new ArgumentException("Quantity is not positive", nameof(config));
if (quantity == 0 || !double.IsRealNumber(quantity)) {
throw new ArgumentException("Quantity must be real and non-zero", nameof(config));
}
this.config = new(venue, contract, account, config.OffsetTicks, quantity);
}
@@ -86,11 +86,12 @@ class TopOfBookQuoter : IHostedService {
if (!bk.Security.HasValue) {
return;
}
var bidPrice = bk[Side.Buy, 0].Price;
var orderPrice = bidPrice;
var orderQty = config.Quantity;
var bidPrice = bk[orderQty < 0 ? Side.Sell : Side.Buy, 0].Price;
orderQty = Math.Abs(orderQty);
var orderPrice = bidPrice;
if (config.OffsetTicks != 0) {
orderPrice = bk.Security.Value.tick_factors().tickdown(orderPrice, config.OffsetTicks);
orderPrice = bk.Security.Value.tick_factors().tick(orderPrice, config.OffsetTicks);
}
if (securityToOrderId.TryGetValue(bk.SecurityId, out var orderId)
&& orders.TryGetValue(orderId, out var orderTree)
+1 -1
View File
@@ -4,7 +4,7 @@ using bts.api;
public static class VenueParser {
public static bool TryParse(string? venueStr, out Venue venue) {
if (venueStr is null || (!Enum.TryParse(venueStr, false, out venue) && !Enum.TryParse("VENUE_" + venueStr, false, out venue))) {
if (venueStr is null || (!Enum.TryParse(venueStr, true, out venue) && !Enum.TryParse("VENUE_" + venueStr, true, out venue))) {
venue = Venue.Unset;
return false;
}
@@ -3,6 +3,7 @@ namespace Bts.Spark.Sdk;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.metrics;
using global::Spark.Api.Admin;
using global::Spark.Api.MarketData;
@@ -28,6 +29,7 @@ public class AuthenticatedSimpleSparkClient : ISimpleSparkClient, IHostedService
readonly IOrdersClient ordersClient;
readonly IAdminClient adminClient;
readonly ISecurityResolver securityResolver;
readonly ShellLatencyService shellLatencyService;
readonly ISparkLoginService loginService;
readonly SimpleSparkClientConfig config;
readonly ILogger<AuthenticatedSimpleSparkClient> logger;
@@ -39,6 +41,7 @@ public class AuthenticatedSimpleSparkClient : ISimpleSparkClient, IHostedService
IOrdersClient ordersClient,
IAdminClient adminClient,
ISecurityResolver securityResolver,
ShellLatencyService shellLatencyService,
ISparkLoginService loginService,
IOptions<SimpleSparkClientConfig> config,
ILogger<AuthenticatedSimpleSparkClient> logger) {
@@ -46,6 +49,7 @@ public class AuthenticatedSimpleSparkClient : ISimpleSparkClient, IHostedService
this.ordersClient = ordersClient;
this.adminClient = adminClient;
this.securityResolver = securityResolver;
this.shellLatencyService = shellLatencyService;
this.loginService = loginService;
this.config = config.Value;
this.logger = logger;
@@ -61,7 +65,7 @@ public class AuthenticatedSimpleSparkClient : ISimpleSparkClient, IHostedService
}
cancellationToken.ThrowIfCancellationRequested();
var user = loginRes.user.Value;
simpleClient = new(user.name, user.id, mdClient, ordersClient, adminClient, securityResolver, config.OrderPoolCapacity, config.OrderPoolReplenishInterval);
simpleClient = new(user.name, user.id, mdClient, ordersClient, shellLatencyService, adminClient, securityResolver, config.OrderPoolCapacity, config.OrderPoolReplenishInterval);
simpleClientSubject.OnNext(simpleClient);
logger.LogInformation("SimpleSparkClient started");
}
@@ -97,6 +101,9 @@ public class AuthenticatedSimpleSparkClient : ISimpleSparkClient, IHostedService
public IObservable<TableUpdate<PriceTicker, uint>> PriceTicker(SecurityCriteria? securityCriteria) =>
simpleClient?.PriceTicker(securityCriteria) ?? simpleClientSubject.Take(1).Select(c => c.PriceTicker(securityCriteria)).FlattenSeries();
public Task<bool> PingAsync() =>
simpleClient?.PingAsync() ?? pingBeforeStartAsync();
public Task<Response> CreateOrder(OrderDefinition orderDef, double quantity, double price) =>
orderOp(cli => cli.CreateOrder(orderDef, quantity, price));
@@ -134,4 +141,15 @@ public class AuthenticatedSimpleSparkClient : ISimpleSparkClient, IHostedService
async Task<T> LateImpl() =>
await op(await simpleClientSubject.FirstAsync());
}
async Task<bool> pingBeforeStartAsync() {
var request = new BufferMessage() {
Sequence = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
Data = "spark-sdk-ping"
};
var result = await shellLatencyService.ping_async(request);
return result.success &&
result.value.Sequence == request.Sequence &&
result.value.Data == request.Data;
}
}
@@ -48,6 +48,9 @@ public class ExecutionFacadeSimpleSparkClient : ISimpleSparkClient {
public IObservable<TableUpdate<PriceTicker, uint>> PriceTicker(SecurityCriteria? securityCriteria) =>
sparkClient.PriceTicker(securityCriteria);
public Task<bool> PingAsync() =>
sparkClient.PingAsync();
public Task<Response> CreateOrder(OrderDefinition orderDef, double quantity, double price) {
logger.LogInformation("CreateOrder {account} {tag} {securityId} {side} {quantity} @ {price}", orderDef.Account, orderDef.Tag, orderDef.SecurityKey, orderDef.Side, quantity, price);
return Task.FromResult(facadeResponse());
@@ -12,6 +12,8 @@ using mbts.spark;
using System;
public interface ISimpleSparkClient {
Task<bool> PingAsync();
// Market Data
IObservable<TableUpdate<SecurityImmutable, uint>> Securities(SecurityCriteria? securityCriteria);
IObservable<TableUpdate<BookRecord, uint>> Books(SecurityCriteria? securityCriteria);
+23 -4
View File
@@ -16,12 +16,14 @@ using global::Spark.Core.Disposables;
using global::Spark.Core.Reactive;
using mbts.spark;
using bts.metrics;
using System.Reactive.Linq;
public class SimpleSparkClient : ISimpleSparkClient, IDisposable {
readonly IMarketDataClient mdClient;
readonly IOrdersClient ordersClient;
readonly ShellLatencyService shellLatencyService;
readonly OrderPool limitOrderPool;
readonly OrderPool marketOrderPool;
readonly TableUpdateDemuxer<OrderRecordTupleWithRoot, uint, uint> orderSecurityDmx;
@@ -46,6 +48,7 @@ public class SimpleSparkClient : ISimpleSparkClient, IDisposable {
uint userId,
IMarketDataClient mdClient,
IOrdersClient ordersClient,
ShellLatencyService shellLatencyService,
IAdminClient adminClient,
ISecurityResolver securityResolver,
int orderPoolCapacity,
@@ -53,6 +56,7 @@ public class SimpleSparkClient : ISimpleSparkClient, IDisposable {
) {
this.mdClient = mdClient;
this.ordersClient = ordersClient;
this.shellLatencyService = shellLatencyService;
limitOrderPool = new(orderPoolCapacity, () => new() { User = userName, UserId = userId, TimeInForce = TimeInForce.Gtc, CustomDef = new() { Limit = new() } });
marketOrderPool = new(orderPoolCapacity, () => new() { User = userName, UserId = userId, TimeInForce = TimeInForce.Gtc, CustomDef = new() { Market = new() } });
orderSecurityDmx = new TableUpdateDemuxer<OrderRecordTupleWithRoot, uint, uint>(o => o.order.order.Key, o => o.security_id);
@@ -140,6 +144,17 @@ public class SimpleSparkClient : ISimpleSparkClient, IDisposable {
}
}
public async Task<bool> PingAsync() {
var request = new BufferMessage() {
Sequence = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
Data = "spark-sdk-ping"
};
var result = await shellLatencyService.ping_async(request);
return result.success &&
result.value.Sequence == request.Sequence &&
result.value.Data == request.Data;
}
public IObservable<TableUpdate<BookRecord, uint>> Books(SecurityCriteria? securityCriteria) {
var booksSource = this.booksSource;
var secFilter = securityCriteria?.ToPredicate();
@@ -271,28 +286,32 @@ public class SimpleSparkClient : ISimpleSparkClient, IDisposable {
public Task<Response> CreateLimitOrder(string account, Venue venue, string contract, Side side, double quantity, double price, string? tag) {
var def = buildOrder(account, venue, contract, limitOrderPool);
def.Side = side;
def.Tag = tag;
if (tag is not null)
def.Tag = tag;
return ordersClient.TryEnterOrderAsync(def, price, quantity);
}
public Task<Response> CreateLimitOrder(string account, uint securityId, Side side, double quantity, double price, string? tag) {
var def = buildOrder(account, securityId, limitOrderPool);
def.Side = side;
def.Tag = tag;
if (tag is not null)
def.Tag = tag;
return ordersClient.TryEnterOrderAsync(def, price, quantity);
}
public Task<Response> CreateMarketOrder(string account, Venue venue, string contract, Side side, double quantity, string? tag) {
var def = buildOrder(account, venue, contract, marketOrderPool);
def.Side = side;
def.Tag = tag;
if (tag is not null)
def.Tag = tag;
return ordersClient.TryEnterOrderAsync(def, double.NaN, quantity);
}
public Task<Response> CreateMarketOrder(string account, uint securityId, Side side, double quantity, string? tag) {
var def = buildOrder(account, securityId, marketOrderPool);
def.Side = side;
def.Tag = tag;
if (tag is not null)
def.Tag = tag;
return ordersClient.TryEnterOrderAsync(def, double.NaN, quantity);
}
@@ -3,9 +3,9 @@
# source: tools/cpp_helper/generate-cpps.ps1
#
CppConfig
{
value
CppConfig
{
value
{
cpp_entry { key: "0A" value: 100 }
cpp_entry { key: "0B" value: 100 }
@@ -537,6 +537,31 @@ CppConfig
cpp_entry { key: "EQ1" value: 50 }
cpp_entry { key: "ERL" value: 5 }
cpp_entry { key: "ES" value: 50 }
cpp_entry { key: "E1A" value: 50}
cpp_entry { key: "E1B" value: 50}
cpp_entry { key: "E1C" value: 50}
cpp_entry { key: "E1D" value: 50}
cpp_entry { key: "E2A" value: 50}
cpp_entry { key: "E2B" value: 50}
cpp_entry { key: "E2C" value: 50}
cpp_entry { key: "E2D" value: 50}
cpp_entry { key: "E3A" value: 50}
cpp_entry { key: "E3B" value: 50}
cpp_entry { key: "E3C" value: 50}
cpp_entry { key: "E3D" value: 50}
cpp_entry { key: "E4A" value: 50}
cpp_entry { key: "E4B" value: 50}
cpp_entry { key: "E4C" value: 50}
cpp_entry { key: "E4D" value: 50}
cpp_entry { key: "E5A" value: 50}
cpp_entry { key: "E5B" value: 50}
cpp_entry { key: "E5C" value: 50}
cpp_entry { key: "E5D" value: 50}
cpp_entry { key: "EW" value: 50}
cpp_entry { key: "EW1" value: 50}
cpp_entry { key: "EW2" value: 50}
cpp_entry { key: "EW3" value: 50}
cpp_entry { key: "EW4" value: 50}
cpp_entry { key: "ES1" value: 50 }
cpp_entry { key: "ES2" value: 50 }
cpp_entry { key: "ESB" value: 1000 }
@@ -1001,6 +1026,31 @@ CppConfig
cpp_entry { key: "NOO" value: 8900 }
cpp_entry { key: "NPG" value: 2500 }
cpp_entry { key: "NQ" value: 20 }
cpp_entry {key: "Q1A" value: 20}
cpp_entry {key: "Q1B" value: 20}
cpp_entry {key: "Q1C" value: 20}
cpp_entry {key: "Q1D" value: 20}
cpp_entry {key: "Q2A" value: 20}
cpp_entry {key: "Q2B" value: 20}
cpp_entry {key: "Q2C" value: 20}
cpp_entry {key: "Q2D" value: 20}
cpp_entry {key: "Q3A" value: 20}
cpp_entry {key: "Q3B" value: 20}
cpp_entry {key: "Q3C" value: 20}
cpp_entry {key: "Q3D" value: 20}
cpp_entry {key: "Q4A" value: 20}
cpp_entry {key: "Q4B" value: 20}
cpp_entry {key: "Q4C" value: 20}
cpp_entry {key: "Q4D" value: 20}
cpp_entry {key: "Q5A" value: 20}
cpp_entry {key: "Q5B" value: 20}
cpp_entry {key: "Q5C" value: 20}
cpp_entry {key: "Q5D" value: 20}
cpp_entry {key: "QNE" value: 20}
cpp_entry {key: "QN1" value: 20}
cpp_entry {key: "QN2" value: 20}
cpp_entry {key: "QN3" value: 20}
cpp_entry {key: "QN4" value: 20}
cpp_entry { key: "NQQ" value: 20 }
cpp_entry { key: "NQT" value: 20 }
cpp_entry { key: "NR" value: 2500 }
@@ -1506,12 +1556,12 @@ CppConfig
cpp_entry { key: "ZT" value: 2000 }
cpp_entry { key: "ZW" value: 50 }
cpp_entry { key: "ZWT" value: 50 }
}
}
}
}
CmeSymbolMics
{
value
CmeSymbolMics
{
value
{
cme_mic { key: "0A" value: NYMEX }
cme_mic { key: "0B" value: NYMEX }
@@ -3012,5 +3062,5 @@ CmeSymbolMics
cme_mic { key: "ZT" value: CBT }
cme_mic { key: "ZW" value: CBT }
cme_mic { key: "ZWT" value: CBT }
}
}
}
@@ -30,5 +30,6 @@ OrdermanCfg {
poll: false
hosted_actor: "*"
}
fallback_cust_order_handling_inst: "Y"
}
}
@@ -5,9 +5,15 @@ FillsSoftCleanup {
}
OrdersSoftCleanup {
value {
enabled: true
}
value {
enabled: true
}
}
Mdp3OptionExpirationCutoff {
value {
max_days_until_expiration: 14
}
}
PlacementInQueue {
value{
@@ -32,11 +32,17 @@ message SoundNotifications {
bool enabled = 1;
}
message FilterExpiredFills {
bool enabled = 1;
}
message VolumeAtPrice {
message FilterExpiredFills {
bool enabled = 1;
}
// MDP3 will not publish CME options whose maturity is beyond this many days.
// A value of zero disables this cutoff.
message Mdp3OptionExpirationCutoff {
uint32 max_days_until_expiration = 1;
}
message VolumeAtPrice {
bool enabled = 1;
}
@@ -124,3 +130,8 @@ message VolSurfaceUsageUnit{
message VolSurfaceUsage{
repeated VolSurfaceUsageUnit items = 1;
}
message LogTrades{
string sec_name = 1;
bool log = 2;
}
@@ -10,6 +10,7 @@
#include "spark/orderman/orderman_utils/orderman_common.h"
#include "spark/tracing/bts_trace.h"
#include "spark/utils/TimeStamp.h"
#include "spark/utils/bts_assert.h"
#include "spark/utils/bts_chrono.h"
#include "spark/utils/datetime.h"
#include "spark/utils/thread_manager.h"
@@ -97,7 +98,8 @@ struct CoreGateway::PImpl {
: cfg(cfg), observer(observer), make_connection(conn_factory),
make_store(store_factory), state(State::OFF), start_of_pause(),
session_info(), event_queue(), timer_thread(), destroy(false),
is_on(false), has_replayed(false), send_batch(), recv_batch(),
is_on(false), has_replayed(false), gave_up_permanently(false),
send_batch(), recv_batch(),
connection_attempts(0), perf_log(l) {
send_batch.reserve(1024);
recv_batch.reserve(1024);
@@ -116,6 +118,7 @@ struct CoreGateway::PImpl {
std::atomic<bool> destroy;
bool is_on;
bool has_replayed;
bool gave_up_permanently;
std::vector<EventData> send_batch;
std::vector<EventData> recv_batch;
uint32_t connection_attempts;
@@ -329,6 +332,11 @@ void CoreGateway::sync_session_ready(EventData const &) {
case State::WSESSION_TERMINATE:
break;
case State::CONNECTING:
LOG(Jetfix, Info,
"Jetfix session {} is now READY after socket connect. attempts={} "
"max_attempts={}",
me->cfg.name(), me->connection_attempts,
me->cfg.max_connection_attempts());
transition(State::READY);
me->observer.ready();
break;
@@ -346,10 +354,18 @@ void CoreGateway::sync_session_terminated(EventData const &) {
break;
case State::WSESSION_TERMINATE:
LOG(Jetfix, Warn,
"Session terminated callback received for {} while waiting for "
"reader disconnect. attempts_so_far={}",
me->cfg.name(), me->connection_attempts);
pause_or_shutdown();
break;
case State::READY:
LOG(Jetfix, Warn,
"Session terminated for {} from READY. Closing socket and waiting "
"for reader disconnect.",
me->cfg.name());
me->session_info->session.reset();
me->session_info->connection->shutdown();
transition(State::WREADER_DISCONNECT);
@@ -357,6 +373,10 @@ void CoreGateway::sync_session_terminated(EventData const &) {
break;
case State::CONNECTING:
LOG(Jetfix, Warn,
"Session terminated for {} during CONNECTING before READY. "
"Closing socket and waiting for reader disconnect.",
me->cfg.name());
me->session_info->session.reset();
me->session_info->connection->shutdown();
transition(State::WREADER_DISCONNECT);
@@ -376,17 +396,28 @@ void CoreGateway::sync_reader_disconnected(EventData const &) {
break;
case State::READY:
LOG(Jetfix, Warn,
"Socket reader disconnected for {} from READY. "
"The exchange or network closed the TCP session.",
me->cfg.name());
me->session_info->session.reset();
me->observer.disconnect();
transition(State::WSESSION_TERMINATE);
break;
case State::CONNECTING:
LOG(Jetfix, Warn,
"Socket reader disconnected for {} during CONNECTING before READY. "
"The TCP socket connected, but the session never completed logon.",
me->cfg.name());
me->session_info->session.reset();
transition(State::WSESSION_TERMINATE);
break;
case State::WREADER_DISCONNECT:
LOG(Jetfix, Info,
"Reader disconnect completed for {} after session shutdown.",
me->cfg.name());
pause_or_shutdown();
break;
@@ -430,27 +461,46 @@ void CoreGateway::sync_in_msg_sequenced(EventData const &ev) {
bts_assert(false);
}
}
void CoreGateway::sync_time(EventData const &ev) {
switch (me->state) {
case State::OFF:
break;
case State::PAUSED:
if (me->gave_up_permanently)
break;
if (datetime::is_at_least_secs_old(
me->start_of_pause, me->cfg.seconds_to_pause())) {
LOG(Jetfix, Info, "Pause period ended for jetfix connection {}",
me->cfg.name());
LOG(Jetfix, Info,
"Pause ended for jetfix connection {}. pause_secs={} "
"attempts_so_far={} max_attempts={} host={}:{} segment={} "
"session_window={}-{} {}-{} replayed={}",
me->cfg.name(), me->cfg.seconds_to_pause(),
me->connection_attempts, me->cfg.max_connection_attempts(),
me->cfg.socket_connect_host_ip(), me->cfg.socket_connect_port(),
me->cfg.cme_market_segment_id(), me->cfg.start_time(),
me->cfg.end_time(), me->cfg.start_day(), me->cfg.end_day(),
me->has_replayed);
me->session_info = std::make_unique<SessionInfo>();
me->session_info->store.reset(me->make_store(me->cfg));
if (me->session_info->store->session_active()) {
auto const session_active = me->session_info->store->session_active();
LOG(Jetfix, Info,
"Loaded FIX store for {}. session_active={} next_attempt={} "
"max_attempts={}",
me->cfg.name(), session_active, me->connection_attempts + 1,
me->cfg.max_connection_attempts());
if (session_active) {
me->connection_attempts++;
LOG(Jetfix, Info,
"Attempting socket connect for jetfix connection {}. "
"attempt={} max_attempts={}",
me->cfg.name(), me->connection_attempts,
me->cfg.max_connection_attempts());
if (!me->has_replayed) {
auto msgs_to_replay = me->session_info->store->replay();
LOG(Jetfix, Warn, "Replaying {} FIX msgs",
msgs_to_replay.size());
LOG(Jetfix, Warn,
"Replaying {} FIX msgs for {} before reconnect",
msgs_to_replay.size(), me->cfg.name());
me->observer.replay(msgs_to_replay);
me->has_replayed = true;
}
@@ -458,14 +508,20 @@ void CoreGateway::sync_time(EventData const &ev) {
if (me->connection_attempts > me->cfg.max_connection_attempts()) {
me->session_info.reset();
me->start_of_pause = datetime::now();
me->gave_up_permanently = true;
LOG(Jetfix, Error,
"Failed to connect to exchange gateway {} too many "
"times. Giving up permanently. You must bounce "
"the server to recover.",
me->cfg.name());
"times. Giving up permanently. attempts={} "
"max_attempts={} pause_secs={} host={}:{} segment={}. "
"You must bounce the server to recover.",
me->cfg.name(), me->connection_attempts,
me->cfg.max_connection_attempts(),
me->cfg.seconds_to_pause(),
me->cfg.socket_connect_host_ip(),
me->cfg.socket_connect_port(),
me->cfg.cme_market_segment_id());
break;
}
me->session_info->connection.reset(me->make_connection(me->cfg));
LOG(Jetfix, Warn, "Trying to connect to exchange for {} ({}/{})",
me->cfg.name(), me->connection_attempts,
@@ -489,11 +545,18 @@ void CoreGateway::sync_time(EventData const &ev) {
me->session_info.reset();
me->start_of_pause = datetime::now();
LOG(Jetfix, Warn,
"Failed to connect to exchange for {}. ({}/{}) ",
"Failed to connect to exchange for {}. ({}/{}) "
"Will retry after {} seconds.",
me->cfg.name(), me->connection_attempts,
me->cfg.max_connection_attempts());
me->cfg.max_connection_attempts(),
me->cfg.seconds_to_pause());
}
} else {
LOG(Jetfix, Warn,
"FIX store for {} is not active yet. Deferring reconnect. "
"session_window={}-{} {}-{}",
me->cfg.name(), me->cfg.start_time(), me->cfg.end_time(),
me->cfg.start_day(), me->cfg.end_day());
me->session_info.reset();
me->start_of_pause = datetime::now();
}
@@ -516,6 +579,12 @@ void CoreGateway::pause_or_shutdown() {
if (me->is_on) {
me->start_of_pause = datetime::now();
LOG(Jetfix, Info,
"Pausing jetfix gateway {}. pause_secs={} attempts_so_far={} "
"max_attempts={} gave_up_permanently={}",
me->cfg.name(), me->cfg.seconds_to_pause(),
me->connection_attempts, me->cfg.max_connection_attempts(),
me->gave_up_permanently);
transition(State::PAUSED);
} else
transition(State::OFF);
@@ -114,6 +114,15 @@ void Session::start() {
me->start_of_logon = me->now();
me->last_seq_num = me->begin_of_this_session;
me->last_seq_num_processed = me->store.last_seq_num_processed();
LOG(Jetfix, Info,
"Starting jetfix session {}. sender_compid={} sender_subid={} "
"target_compid={} target_subid={} secure_logon={} raw_data_len={} "
"user_name_present={} seq_num={} last_processed={}",
me->cfg.name(), me->cfg.sender_compid(), me->cfg.sender_subid(),
me->cfg.target_compid(), me->cfg.target_subid(),
me->cfg.use_cme_secure_logon(), me->cfg.raw_data().size(),
!me->cfg.user_name().empty(), me->last_seq_num + 1,
me->last_seq_num_processed);
transition(State::WLOGON);
internal_send(make_logon(
me->last_seq_num + 1, me->last_seq_num_processed, *me->current_time,
@@ -1,4 +1,5 @@
#include "spark/logging/logging.h"
#include "spark/md/log_messages/trade_latency_log.h"
#ifndef NO_CPP0X
#include "MDProducerHandle.h"
#include "common.h"
@@ -133,6 +134,10 @@ void MDProducerHandle::add_trade(
trade.set_key(key());
_channel.add_trade(trade);
}
log_trade_latency(
TradeLatencyStage::SHM_WRITTEN, security(), tradetime, price, agside,
volume, from_spread);
}
void MDProducerHandle::set_last_trade(
TimeStamp time,
@@ -0,0 +1,124 @@
#pragma once
#include "spark/config/config.h"
#include "spark/config/protos/toggles.pb.h"
#include "spark/core/FMT_IMPL.h"
#include "spark/core/time_stamp.h"
#include "spark/logging/logging.h"
#include "spark/md/PriceSize.h"
#include "spark/md/Shm/FixedSecurity.h"
#include "spark/md/protos/aggressor_side.h"
#include "spark/utils/datetime.h"
#include <string>
#include <cstdint>
#include <utility>
namespace bts::md {
enum class TradeLatencyStage {
MDP3_RECEIVED,
SHM_WRITTEN,
};
inline const char *TradeLatencyStage_Name(TradeLatencyStage stage) {
switch (stage) {
case TradeLatencyStage::MDP3_RECEIVED:
return "mdp3_received";
case TradeLatencyStage::SHM_WRITTEN:
return "shm_written";
}
return "unknown";
}
inline std::string format_ll_datetime(datetime::LLDateTime ts) {
return datetime::iso_date_time_string(datetime::ll_to_ptime(ts));
}
inline bool should_log_trade_latency(const FixedSecurity &security) {
const auto &cfg = config::get<config::toggles::LogTrades>();
if (!cfg.log() || cfg.sec_name().empty()) {
return false;
}
return cfg.sec_name() == security.security_desc() ||
cfg.sec_name() == security.symbol();
}
struct TradeLatencyLog {
TradeLatencyStage stage;
std::string sec_name;
uint32_t security_key;
int64_t trade_ts_unix_ns;
std::string trade_ts_iso;
int64_t log_ts_unix_ns;
std::string log_ts_iso;
uint64_t monotonic_log_ts_ns;
int64_t exchange_to_log_us;
double price;
double quantity;
std::string aggressor_side;
uint32_t total_volume;
bool from_spread;
TradeLatencyLog(
TradeLatencyStage stage,
const FixedSecurity &security,
datetime::LLDateTime trade_ts,
core::TimeStamp log_ts,
utils::TimeStamp monotonic_log_ts,
const PriceSize &price_size,
co2::AggressorSide aggressor_side,
uint32_t total_volume,
bool from_spread)
: stage(stage), sec_name(security.security_desc()),
security_key(security.key), trade_ts_unix_ns(trade_ts),
trade_ts_iso(format_ll_datetime(trade_ts)),
log_ts_unix_ns(log_ts.nanos()), log_ts_iso(log_ts.to_iso_string()),
monotonic_log_ts_ns(monotonic_log_ts.to_uint64()),
exchange_to_log_us(
(log_ts - core::TimeStamp::from_ptime(datetime::ll_to_ptime(trade_ts)))
.total_micros()),
price(price_size.price()), quantity(price_size.size()),
aggressor_side(co2::AggressorSide_Name(aggressor_side)),
total_volume(total_volume), from_spread(from_spread) {}
};
inline void log_trade_latency(
TradeLatencyStage stage,
const FixedSecurity &security,
datetime::LLDateTime trade_ts,
const PriceSize &price_size,
co2::AggressorSide aggressor_side,
uint32_t total_volume,
bool from_spread) {
if (!should_log_trade_latency(security)) {
return;
}
const auto log_ts = core::TimeStamp::now();
const auto monotonic_log_ts = utils::TimeStamp::now();
LOG(md, Info, "[trade_latency.json]{}",
TradeLatencyLog(
stage, security, trade_ts, log_ts, monotonic_log_ts, price_size,
aggressor_side, total_volume, from_spread));
}
} // namespace bts::md
FMT_IMPL(
bts::md::TradeLatencyLog,
l,
R"({{ "stage": "{}", "sec": "{}", "key": {}, "tradeTsUnixNs": {}, "tradeTsIso": "{}", "logTsUnixNs": {}, "logTsIso": "{}", "logMonoNs": {}, "exchangeToLogUs": {}, "price": {}, "qty": {}, "agg": "{}", "totalVolume": {}, "fromSpread": {} }})",
bts::md::TradeLatencyStage_Name(l.stage),
l.sec_name,
l.security_key,
l.trade_ts_unix_ns,
l.trade_ts_iso,
l.log_ts_unix_ns,
l.log_ts_iso,
l.monotonic_log_ts_ns,
l.exchange_to_log_us,
l.price,
l.quantity,
l.aggressor_side,
l.total_volume,
l.from_spread);
@@ -23,6 +23,7 @@
#include "spark/utils/overloaded.h"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <cctype>
#include <google/protobuf/timestamp.pb.h>
#include <string>
#include <sys/time.h>
@@ -308,15 +309,60 @@ void FillStore::on_execution_report(
auto exchange_ts = std::visit(
overloaded{
[](const orderman::ExchangeTimeStampStr &ts) {
return utils::pb::to_timestamp(
[&](const orderman::ExchangeTimeStampStr &ts) {
auto gts = utils::pb::to_timestamp(
datetime::parse_FIX_timestamp(to_str(ts)));
const std::string ts_str = to_str(ts);
try {
const auto dot_pos = ts_str.find('.');
if (dot_pos != std::string::npos) {
std::string frac_digits;
for (std::size_t i = dot_pos + 1;
i < ts_str.size() &&
std::isdigit(static_cast<unsigned char>(
ts_str[i]));
++i) {
frac_digits.push_back(ts_str[i]);
}
if (!frac_digits.empty()) {
if (frac_digits.size() > 9) {
frac_digits.resize(9);
}
while (frac_digits.size() < 9) {
frac_digits.push_back('0');
}
gts.set_nanos(
static_cast<int32_t>(
std::stoul(frac_digits)));
}
}
} catch (const std::exception &e) {
WARN_FN(
"on_execution_report",
"fill {} transact_time(str) raw='{}' "
"failed to inspect fractional nanos: {}",
fk, ts_str, std::string(e.what()));
}
INFO_FN(
"on_execution_report",
"fill {} transact_time(str) raw='{}' "
"converted_seconds={} converted_nanos={}",
fk, ts_str, gts.seconds(), gts.nanos());
return gts;
},
[](ExchangeTimeStamp64 ts) {
[&](ExchangeTimeStamp64 ts) {
google::protobuf::Timestamp gts;
auto seconds = ts.value() / 1E9;
static constexpr std::uint64_t nanos_per_second =
1'000'000'000ULL;
auto seconds = ts.value() / nanos_per_second;
gts.set_seconds(seconds);
gts.set_nanos(ts.value() - (seconds * 1E9));
gts.set_nanos(ts.value() % nanos_per_second);
INFO_FN(
"on_execution_report",
"fill {} transact_time(u64) raw_ns={} "
"converted_seconds={} converted_nanos={}",
fk, ts.value(), gts.seconds(), gts.nanos());
return gts;
}},
er.transact_time);
+12 -12
View File
@@ -47,7 +47,7 @@ IL3GW::IL3GW(
: _config(conf), _x_state(State::INIT), _buffer(buffer),
_on_state_change([](auto) {}), _on_exe_report([](auto) {}),
_on_session_change([](auto) {}), _connection(con), _session(session),
_msg_count(0), _time(), _pool(), _exe_parser(cfg, db), _retransmit() {
_msg_count(0), _time(), _pool("IL3GW_rprts", 1024*2, 100), _exe_parser(cfg, db), _retransmit() {
LOG(OrderMan, Info,
"IL3GW::IL3GW cme_session: {} uuid: {} to_cme: {} from_cme: {}",
_config.cme_session, _session.uuid, _session.to_cme, _session.from_cme);
@@ -179,15 +179,10 @@ uint64_t IL3GW::send_party_details(const api::orderman::RoutingRecord &route) {
using namespace iLinkBinary;
auto msg = _buffer.frame<PartyDetailsDefinitionRequest518>(
// 2 repeating group header.
// 1 for party details and 1 for trade reg
// This seems unsafe but the api doesn't seem to want you to know how to
// size buffers ahead of time
2 * PartyDetailsDefinitionRequest518::NoPartyDetails::sbeHeaderSize() +
// N * size of each block
3 * PartyDetailsDefinitionRequest518::NoPartyDetails::sbeBlockLength() +
0 * PartyDetailsDefinitionRequest518::NoTrdRegPublications::
sbeBlockLength());
// Use the generated helper so group sizing stays in sync with the
// active schema when CME updates the template layout.
PartyDetailsDefinitionRequest518::computeLength(3, 0) -
PartyDetailsDefinitionRequest518::sbeBlockLength());
msg.partyDetailsListReqID(0);
msg.sendingTimeEpoch(_time.now());
msg.listUpdateAction(ListUpdAct::Add);
@@ -637,8 +632,13 @@ void IL3GW::handle(
handle_common(msg);
}
void IL3GW::handle(const iLinkBinary::BusinessReject521 &msg) {
LOG(iLink3, Error, "Business Reject: {} '{}'.", msg.businessRejectReason(),
string{translate_business_reject_reason(msg.businessRejectReason())});
LOG(iLink3, Error,
"Business Reject: reason={} '{}' ref_seq={} ref_tag={} ref_msg_type='{}' "
"ref_id={} text='{}'",
msg.businessRejectReason(),
string{translate_business_reject_reason(msg.businessRejectReason())},
msg.refSeqNum(), msg.refTagID(), msg.getRefMsgTypeAsStringView(),
msg.businessRejectRefID(), msg.getTextAsStringView());
handle_common(msg);
// A business reject can de-sync the sequence number because it rejects
// the sequence number of the rejected message. Send a sequence message
@@ -70,9 +70,35 @@ bool IL3Parser::parse_common(const T &msg, ExecutionReport *data) {
data->ice_cl_ord_id = 0;
data->ice_company_id = 0;
data->cfg = cfg;
data->fill_agg_flag = api::orderman::PASSIVE;
return true;
}
template <class T>
void IL3Parser::parse_fill_aggressor(const T &, ExecutionReport *data) {
data->fill_agg_flag = api::orderman::PASSIVE;
}
template <>
void IL3Parser::parse_fill_aggressor(
const iLinkBinary::ExecutionReportTradeOutright525 &msg,
ExecutionReport *data) {
data->fill_agg_flag =
msg.aggressorIndicator() == iLinkBinary::BooleanFlag::True
? api::orderman::AGGRESSOR
: api::orderman::PASSIVE;
}
template <>
void IL3Parser::parse_fill_aggressor(
const iLinkBinary::ExecutionReportTradeSpread526 &msg,
ExecutionReport *data) {
data->fill_agg_flag =
msg.aggressorIndicator() == iLinkBinary::BooleanFlag::True
? api::orderman::AGGRESSOR
: api::orderman::PASSIVE;
}
template <class T>
void IL3Parser::parse_trade(const T &msg, ExecutionReport *data) {
data->exchange_fill_id->copy_from(msg.getExecIDAsStringView());
@@ -103,6 +129,7 @@ void IL3Parser::parse_trade(const T &msg, ExecutionReport *data) {
Price{price.mantissa() * md::pow10(price.exponent())});
}
data->filled_quantity = Quantity{msg.cumQty()};
parse_fill_aggressor(msg, data);
}
bool IL3Parser::has_account() const {
@@ -47,6 +47,8 @@ class IL3Parser {
private:
template <class T> bool parse_common(const T &, ExecutionReport *);
template <class T> void parse_trade(const T &, ExecutionReport *);
template <class T>
void parse_fill_aggressor(const T &, ExecutionReport *);
};
bool parse_report(const std::string &, IL3Parser &, ExecutionReport *);
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -168,7 +170,7 @@ public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(330);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(521);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -32,7 +32,7 @@ public:
{
GiveUp = static_cast<char>(71),
SGXoffset = static_cast<char>(83),
NULL_VALUE = static_cast<char>(0x00)
NULL_VALUE = static_cast<char>(0)
};
static CmtaGiveUpCD::Value get(const char value)
@@ -41,7 +41,7 @@ public:
{
case static_cast<char>(71): return GiveUp;
case static_cast<char>(83): return SGXoffset;
case static_cast<char>(0x00): return NULL_VALUE;
case static_cast<char>(0): return NULL_VALUE;
}
throw std::runtime_error("unknown value for enum CmtaGiveUpCD [E103]");
@@ -0,0 +1,75 @@
/* Generated SBE (Simple Binary Encoding) message codec */
#ifndef _ILINKBINARY_CROSSTYPEENUM_H_
#define _ILINKBINARY_CROSSTYPEENUM_H_
#if !defined(__STDC_LIMIT_MACROS)
# define __STDC_LIMIT_MACROS 1
#endif
#include <cstdint>
#include <iomanip>
#include <limits>
#include <ostream>
#include <stdexcept>
#include <sstream>
#include <string>
#define SBE_NULLVALUE_INT8 (std::numeric_limits<std::int8_t>::min)()
#define SBE_NULLVALUE_INT16 (std::numeric_limits<std::int16_t>::min)()
#define SBE_NULLVALUE_INT32 (std::numeric_limits<std::int32_t>::min)()
#define SBE_NULLVALUE_INT64 (std::numeric_limits<std::int64_t>::min)()
#define SBE_NULLVALUE_UINT8 (std::numeric_limits<std::uint8_t>::max)()
#define SBE_NULLVALUE_UINT16 (std::numeric_limits<std::uint16_t>::max)()
#define SBE_NULLVALUE_UINT32 (std::numeric_limits<std::uint32_t>::max)()
#define SBE_NULLVALUE_UINT64 (std::numeric_limits<std::uint64_t>::max)()
namespace iLinkBinary {
class CrossTypeEnum
{
public:
enum Value
{
Cross = static_cast<std::uint8_t>(3),
RCross = static_cast<std::uint8_t>(20),
CCross = static_cast<std::uint8_t>(21),
NULL_VALUE = static_cast<std::uint8_t>(255)
};
static CrossTypeEnum::Value get(const std::uint8_t value)
{
switch (value)
{
case static_cast<std::uint8_t>(3): return Cross;
case static_cast<std::uint8_t>(20): return RCross;
case static_cast<std::uint8_t>(21): return CCross;
case static_cast<std::uint8_t>(255): return NULL_VALUE;
}
throw std::runtime_error("unknown value for enum CrossTypeEnum [E103]");
}
static const char *c_str(const CrossTypeEnum::Value value)
{
switch (value)
{
case Cross: return "Cross";
case RCross: return "RCross";
case CCross: return "CCross";
case NULL_VALUE: return "NULL_VALUE";
}
throw std::runtime_error("unknown value for enum CrossTypeEnum [E103]:");
}
template<typename CharT, typename Traits>
friend std::basic_ostream<CharT, Traits> & operator << (
std::basic_ostream<CharT, Traits> &os, CrossTypeEnum::Value m)
{
return os << CrossTypeEnum::c_str(m);
}
};
}
#endif
@@ -36,7 +36,7 @@ public:
AlgoEngine = static_cast<char>(72),
DeskElectronic = static_cast<char>(87),
ClientElectronic = static_cast<char>(89),
NULL_VALUE = static_cast<char>(48)
NULL_VALUE = static_cast<char>(0)
};
static CustOrdHandlInst::Value get(const char value)
@@ -49,7 +49,7 @@ public:
case static_cast<char>(72): return AlgoEngine;
case static_cast<char>(87): return DeskElectronic;
case static_cast<char>(89): return ClientElectronic;
case static_cast<char>(48): return NULL_VALUE;
case static_cast<char>(0): return NULL_VALUE;
}
throw std::runtime_error("unknown value for enum CustOrdHandlInst [E103]");
@@ -194,7 +194,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static const char *lengthMetaAttribute(const MetaAttribute metaAttribute) SBE_NOEXCEPT
@@ -38,7 +38,7 @@ public:
CalculationDifference = static_cast<char>(70),
NoMatchingExecutionReport = static_cast<char>(71),
Other = static_cast<char>(90),
NULL_VALUE = static_cast<char>(48)
NULL_VALUE = static_cast<char>(0)
};
static DKReason::Value get(const char value)
@@ -53,7 +53,7 @@ public:
case static_cast<char>(70): return CalculationDifference;
case static_cast<char>(71): return NoMatchingExecutionReport;
case static_cast<char>(90): return Other;
case static_cast<char>(48): return NULL_VALUE;
case static_cast<char>(0): return NULL_VALUE;
}
throw std::runtime_error("unknown value for enum DKReason [E103]");
@@ -194,7 +194,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static const char *mantissaMetaAttribute(const MetaAttribute metaAttribute) SBE_NOEXCEPT
@@ -194,7 +194,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static const char *mantissaMetaAttribute(const MetaAttribute metaAttribute) SBE_NOEXCEPT
@@ -213,7 +213,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint64_t mantissaSinceVersion() SBE_NOEXCEPT
{
return 6;
return 9;
}
SBE_NODISCARD bool mantissaInActingVersion() SBE_NOEXCEPT
@@ -255,7 +255,7 @@ public:
SBE_NODISCARD std::int64_t mantissa() const SBE_NOEXCEPT
{
if (m_actingVersion < 6)
if (m_actingVersion < 9)
{
return INT64_C(9223372036854775807);
}
@@ -288,7 +288,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint64_t exponentSinceVersion() SBE_NOEXCEPT
{
return 6;
return 9;
}
SBE_NODISCARD bool exponentInActingVersion() SBE_NOEXCEPT
@@ -330,7 +330,7 @@ public:
SBE_NODISCARD std::int8_t exponent() const SBE_NOEXCEPT
{
if (m_actingVersion < 6)
if (m_actingVersion < 9)
{
return static_cast<std::int8_t>(127);
}
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -168,7 +170,7 @@ public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(132);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(503);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -165,10 +167,10 @@ private:
}
public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(38);
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(39);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(504);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -222,7 +224,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeBlockLength() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(38);
return static_cast<std::uint16_t>(39);
}
SBE_NODISCARD static SBE_CONSTEXPR std::uint64_t sbeBlockAndHeaderLength() SBE_NOEXCEPT
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -968,6 +970,82 @@ public:
return *this;
}
SBE_NODISCARD static const char *EnvironmentIndicatorMetaAttribute(const MetaAttribute metaAttribute) SBE_NOEXCEPT
{
switch (metaAttribute)
{
case MetaAttribute::SEMANTIC_TYPE: return "int";
case MetaAttribute::PRESENCE: return "optional";
default: return "";
}
}
static SBE_CONSTEXPR std::uint16_t environmentIndicatorId() SBE_NOEXCEPT
{
return 39032;
}
SBE_NODISCARD static SBE_CONSTEXPR std::uint64_t environmentIndicatorSinceVersion() SBE_NOEXCEPT
{
return 9;
}
SBE_NODISCARD bool environmentIndicatorInActingVersion() SBE_NOEXCEPT
{
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-compare"
#endif
return m_actingVersion >= environmentIndicatorSinceVersion();
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
}
SBE_NODISCARD static SBE_CONSTEXPR std::size_t environmentIndicatorEncodingOffset() SBE_NOEXCEPT
{
return 38;
}
static SBE_CONSTEXPR std::uint8_t environmentIndicatorNullValue() SBE_NOEXCEPT
{
return static_cast<std::uint8_t>(255);
}
static SBE_CONSTEXPR std::uint8_t environmentIndicatorMinValue() SBE_NOEXCEPT
{
return static_cast<std::uint8_t>(0);
}
static SBE_CONSTEXPR std::uint8_t environmentIndicatorMaxValue() SBE_NOEXCEPT
{
return static_cast<std::uint8_t>(254);
}
static SBE_CONSTEXPR std::size_t environmentIndicatorEncodingLength() SBE_NOEXCEPT
{
return 1;
}
SBE_NODISCARD std::uint8_t environmentIndicator() const SBE_NOEXCEPT
{
if (m_actingVersion < 9)
{
return static_cast<std::uint8_t>(255);
}
std::uint8_t val;
std::memcpy(&val, m_buffer + m_offset + 38, sizeof(std::uint8_t));
return (val);
}
EstablishmentAck504 &environmentIndicator(const std::uint8_t value) SBE_NOEXCEPT
{
std::uint8_t val = (value);
std::memcpy(m_buffer + m_offset + 38, &val, sizeof(std::uint8_t));
return *this;
}
template<typename CharT, typename Traits>
friend std::basic_ostream<CharT, Traits> & operator << (
std::basic_ostream<CharT, Traits> &builder, EstablishmentAck504 _writer)
@@ -1020,6 +1098,10 @@ friend std::basic_ostream<CharT, Traits> & operator << (
builder << R"("SplitMsg": )";
builder << '"' << writer.splitMsg() << '"';
builder << ", ";
builder << R"("EnvironmentIndicator": )";
builder << +writer.environmentIndicator();
builder << '}';
return builder;
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -165,10 +167,10 @@ private:
}
public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(72);
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(73);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(505);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -222,7 +224,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeBlockLength() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(72);
return static_cast<std::uint16_t>(73);
}
SBE_NODISCARD static SBE_CONSTEXPR std::uint64_t sbeBlockAndHeaderLength() SBE_NOEXCEPT
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -961,6 +963,82 @@ public:
return *this;
}
SBE_NODISCARD static const char *EnvironmentIndicatorMetaAttribute(const MetaAttribute metaAttribute) SBE_NOEXCEPT
{
switch (metaAttribute)
{
case MetaAttribute::SEMANTIC_TYPE: return "int";
case MetaAttribute::PRESENCE: return "optional";
default: return "";
}
}
static SBE_CONSTEXPR std::uint16_t environmentIndicatorId() SBE_NOEXCEPT
{
return 39032;
}
SBE_NODISCARD static SBE_CONSTEXPR std::uint64_t environmentIndicatorSinceVersion() SBE_NOEXCEPT
{
return 9;
}
SBE_NODISCARD bool environmentIndicatorInActingVersion() SBE_NOEXCEPT
{
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-compare"
#endif
return m_actingVersion >= environmentIndicatorSinceVersion();
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
}
SBE_NODISCARD static SBE_CONSTEXPR std::size_t environmentIndicatorEncodingOffset() SBE_NOEXCEPT
{
return 72;
}
static SBE_CONSTEXPR std::uint8_t environmentIndicatorNullValue() SBE_NOEXCEPT
{
return static_cast<std::uint8_t>(255);
}
static SBE_CONSTEXPR std::uint8_t environmentIndicatorMinValue() SBE_NOEXCEPT
{
return static_cast<std::uint8_t>(0);
}
static SBE_CONSTEXPR std::uint8_t environmentIndicatorMaxValue() SBE_NOEXCEPT
{
return static_cast<std::uint8_t>(254);
}
static SBE_CONSTEXPR std::size_t environmentIndicatorEncodingLength() SBE_NOEXCEPT
{
return 1;
}
SBE_NODISCARD std::uint8_t environmentIndicator() const SBE_NOEXCEPT
{
if (m_actingVersion < 9)
{
return static_cast<std::uint8_t>(255);
}
std::uint8_t val;
std::memcpy(&val, m_buffer + m_offset + 72, sizeof(std::uint8_t));
return (val);
}
EstablishmentReject505 &environmentIndicator(const std::uint8_t value) SBE_NOEXCEPT
{
std::uint8_t val = (value);
std::memcpy(m_buffer + m_offset + 72, &val, sizeof(std::uint8_t));
return *this;
}
template<typename CharT, typename Traits>
friend std::basic_ostream<CharT, Traits> & operator << (
std::basic_ostream<CharT, Traits> &builder, EstablishmentReject505 _writer)
@@ -1006,6 +1084,10 @@ friend std::basic_ostream<CharT, Traits> & operator << (
builder << R"("SplitMsg": )";
builder << '"' << writer.splitMsg() << '"';
builder << ", ";
builder << R"("EnvironmentIndicator": )";
builder << +writer.environmentIndicator();
builder << '}';
return builder;
@@ -194,7 +194,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
ExecInst &clear()
@@ -32,7 +32,7 @@ public:
{
Aggressive = static_cast<char>(65),
Passive = static_cast<char>(80),
NULL_VALUE = static_cast<char>(0x00)
NULL_VALUE = static_cast<char>(0)
};
static ExecMode::Value get(const char value)
@@ -41,7 +41,7 @@ public:
{
case static_cast<char>(65): return Aggressive;
case static_cast<char>(80): return Passive;
case static_cast<char>(0x00): return NULL_VALUE;
case static_cast<char>(0): return NULL_VALUE;
}
throw std::runtime_error("unknown value for enum ExecMode [E103]");
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -168,7 +170,7 @@ public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(101);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(539);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -168,7 +170,7 @@ public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(247);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(534);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -168,7 +170,7 @@ public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(219);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(524);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -168,7 +170,7 @@ public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(226);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(531);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -168,7 +170,7 @@ public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(226);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(522);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -168,7 +170,7 @@ public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(219);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(564);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -168,7 +170,7 @@ public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(196);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(565);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -168,7 +170,7 @@ public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(483);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(523);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -168,7 +170,7 @@ public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(513);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(532);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -127,7 +127,9 @@
#include "QuoteAckStatus.h"
#include "ExpCycle.h"
#include "MassCancelTIF.h"
#include "Side.h"
#include "OrderStatus.h"
#include "CrossTypeEnum.h"
#include "ShortSaleType.h"
#include "PartyDetailRole.h"
#include "MassActionScope.h"
@@ -165,10 +167,10 @@ private:
}
public:
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(222);
static const std::uint16_t SBE_BLOCK_LENGTH = static_cast<std::uint16_t>(266);
static const std::uint16_t SBE_TEMPLATE_ID = static_cast<std::uint16_t>(548);
static const std::uint16_t SBE_SCHEMA_ID = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(8);
static const std::uint16_t SBE_SCHEMA_VERSION = static_cast<std::uint16_t>(9);
enum MetaAttribute
{
@@ -222,7 +224,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeBlockLength() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(222);
return static_cast<std::uint16_t>(266);
}
SBE_NODISCARD static SBE_CONSTEXPR std::uint64_t sbeBlockAndHeaderLength() SBE_NOEXCEPT
@@ -242,7 +244,7 @@ public:
SBE_NODISCARD static SBE_CONSTEXPR std::uint16_t sbeSchemaVersion() SBE_NOEXCEPT
{
return static_cast<std::uint16_t>(8);
return static_cast<std::uint16_t>(9);
}
SBE_NODISCARD static SBE_CONSTEXPR const char *sbeSemanticType() SBE_NOEXCEPT
@@ -3110,7 +3112,7 @@ public:
{
switch (metaAttribute)
{
case MetaAttribute::SEMANTIC_TYPE: return "int";
case MetaAttribute::SEMANTIC_TYPE: return "Qty";
case MetaAttribute::PRESENCE: return "required";
default: return "";
}
@@ -3157,7 +3159,7 @@ public:
{
switch (metaAttribute)
{
case MetaAttribute::SEMANTIC_TYPE: return "int";
case MetaAttribute::SEMANTIC_TYPE: return "Qty";
case MetaAttribute::PRESENCE: return "required";
default: return "";
}
@@ -3247,6 +3249,313 @@ public:
return m_benchmarkPrice;
}
SBE_NODISCARD static const char *MDTradeEntryIDMetaAttribute(const MetaAttribute metaAttribute) SBE_NOEXCEPT
{
switch (metaAttribute)
{
case MetaAttribute::SEMANTIC_TYPE: return "int";
case MetaAttribute::PRESENCE: return "required";
default: return "";
}
}
static SBE_CONSTEXPR std::uint16_t mDTradeEntryIDId() SBE_NOEXCEPT
{
return 37711;
}
SBE_NODISCARD static SBE_CONSTEXPR std::uint64_t mDTradeEntryIDSinceVersion() SBE_NOEXCEPT
{
return 9;
}
SBE_NODISCARD bool mDTradeEntryIDInActingVersion() SBE_NOEXCEPT
{
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-compare"
#endif
return m_actingVersion >= mDTradeEntryIDSinceVersion();
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
}
SBE_NODISCARD static SBE_CONSTEXPR std::size_t mDTradeEntryIDEncodingOffset() SBE_NOEXCEPT
{
return 222;
}
static SBE_CONSTEXPR std::uint32_t mDTradeEntryIDNullValue() SBE_NOEXCEPT
{
return SBE_NULLVALUE_UINT32;
}
static SBE_CONSTEXPR std::uint32_t mDTradeEntryIDMinValue() SBE_NOEXCEPT
{
return UINT32_C(0x0);
}
static SBE_CONSTEXPR std::uint32_t mDTradeEntryIDMaxValue() SBE_NOEXCEPT
{
return UINT32_C(0xfffffffe);
}
static SBE_CONSTEXPR std::size_t mDTradeEntryIDEncodingLength() SBE_NOEXCEPT
{
return 4;
}
SBE_NODISCARD std::uint32_t mDTradeEntryID() const SBE_NOEXCEPT
{
if (m_actingVersion < 9)
{
return UINT32_C(0xffffffff);
}
std::uint32_t val;
std::memcpy(&val, m_buffer + m_offset + 222, sizeof(std::uint32_t));
return SBE_LITTLE_ENDIAN_ENCODE_32(val);
}
ExecutionReportTradeAddendumOutright548 &mDTradeEntryID(const std::uint32_t value) SBE_NOEXCEPT
{
std::uint32_t val = SBE_LITTLE_ENDIAN_ENCODE_32(value);
std::memcpy(m_buffer + m_offset + 222, &val, sizeof(std::uint32_t));
return *this;
}
SBE_NODISCARD static const char *ExecRefIDMetaAttribute(const MetaAttribute metaAttribute) SBE_NOEXCEPT
{
switch (metaAttribute)
{
case MetaAttribute::SEMANTIC_TYPE: return "String";
case MetaAttribute::PRESENCE: return "required";
default: return "";
}
}
static SBE_CONSTEXPR std::uint16_t execRefIDId() SBE_NOEXCEPT
{
return 19;
}
SBE_NODISCARD static SBE_CONSTEXPR std::uint64_t execRefIDSinceVersion() SBE_NOEXCEPT
{
return 9;
}
SBE_NODISCARD bool execRefIDInActingVersion() SBE_NOEXCEPT
{
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-compare"
#endif
return m_actingVersion >= execRefIDSinceVersion();
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
}
SBE_NODISCARD static SBE_CONSTEXPR std::size_t execRefIDEncodingOffset() SBE_NOEXCEPT
{
return 226;
}
static SBE_CONSTEXPR char execRefIDNullValue() SBE_NOEXCEPT
{
return static_cast<char>(0);
}
static SBE_CONSTEXPR char execRefIDMinValue() SBE_NOEXCEPT
{
return static_cast<char>(32);
}
static SBE_CONSTEXPR char execRefIDMaxValue() SBE_NOEXCEPT
{
return static_cast<char>(126);
}
static SBE_CONSTEXPR std::size_t execRefIDEncodingLength() SBE_NOEXCEPT
{
return 40;
}
static SBE_CONSTEXPR std::uint64_t execRefIDLength() SBE_NOEXCEPT
{
return 40;
}
SBE_NODISCARD const char *execRefID() const SBE_NOEXCEPT
{
if (m_actingVersion < 9)
{
return nullptr;
}
return m_buffer + m_offset + 226;
}
SBE_NODISCARD char *execRefID() SBE_NOEXCEPT
{
if (m_actingVersion < 9)
{
return nullptr;
}
return m_buffer + m_offset + 226;
}
SBE_NODISCARD char execRefID(const std::uint64_t index) const
{
if (index >= 40)
{
throw std::runtime_error("index out of range for execRefID [E104]");
}
if (m_actingVersion < 9)
{
return static_cast<char>(0);
}
char val;
std::memcpy(&val, m_buffer + m_offset + 226 + (index * 1), sizeof(char));
return (val);
}
ExecutionReportTradeAddendumOutright548 &execRefID(const std::uint64_t index, const char value)
{
if (index >= 40)
{
throw std::runtime_error("index out of range for execRefID [E105]");
}
char val = (value);
std::memcpy(m_buffer + m_offset + 226 + (index * 1), &val, sizeof(char));
return *this;
}
std::uint64_t getExecRefID(char *const dst, const std::uint64_t length) const
{
if (length > 40)
{
throw std::runtime_error("length too large for getExecRefID [E106]");
}
if (m_actingVersion < 9)
{
return 0;
}
std::memcpy(dst, m_buffer + m_offset + 226, sizeof(char) * static_cast<std::size_t>(length));
return length;
}
ExecutionReportTradeAddendumOutright548 &putExecRefID(const char *const src) SBE_NOEXCEPT
{
std::memcpy(m_buffer + m_offset + 226, src, sizeof(char) * 40);
return *this;
}
SBE_NODISCARD std::string getExecRefIDAsString() const
{
const char *buffer = m_buffer + m_offset + 226;
std::size_t length = 0;
for (; length < 40 && *(buffer + length) != '\0'; ++length);
std::string result(buffer, length);
return result;
}
std::string getExecRefIDAsJsonEscapedString()
{
if (m_actingVersion < 9)
{
return std::string("");
}
std::ostringstream oss;
std::string s = getExecRefIDAsString();
for (const auto c : s)
{
switch (c)
{
case '"': oss << "\\\""; break;
case '\\': oss << "\\\\"; break;
case '\b': oss << "\\b"; break;
case '\f': oss << "\\f"; break;
case '\n': oss << "\\n"; break;
case '\r': oss << "\\r"; break;
case '\t': oss << "\\t"; break;
default:
if ('\x00' <= c && c <= '\x1f')
{
oss << "\\u" << std::hex << std::setw(4)
<< std::setfill('0') << (int)(c);
}
else
{
oss << c;
}
}
}
return oss.str();
}
#if __cplusplus >= 201703L
SBE_NODISCARD std::string_view getExecRefIDAsStringView() const SBE_NOEXCEPT
{
const char *buffer = m_buffer + m_offset + 226;
std::size_t length = 0;
for (; length < 40 && *(buffer + length) != '\0'; ++length);
std::string_view result(buffer, length);
return result;
}
#endif
#if __cplusplus >= 201703L
ExecutionReportTradeAddendumOutright548 &putExecRefID(const std::string_view str)
{
const std::size_t srcLength = str.length();
if (srcLength > 40)
{
throw std::runtime_error("string too large for putExecRefID [E106]");
}
std::memcpy(m_buffer + m_offset + 226, str.data(), srcLength);
for (std::size_t start = srcLength; start < 40; ++start)
{
m_buffer[m_offset + 226 + start] = 0;
}
return *this;
}
#else
ExecutionReportTradeAddendumOutright548 &putExecRefID(const std::string &str)
{
const std::size_t srcLength = str.length();
if (srcLength > 40)
{
throw std::runtime_error("string too large for putExecRefID [E106]");
}
std::memcpy(m_buffer + m_offset + 226, str.c_str(), srcLength);
for (std::size_t start = srcLength; start < 40; ++start)
{
m_buffer[m_offset + 226 + start] = 0;
}
return *this;
}
#endif
class NoFills
{
private:
@@ -4639,7 +4948,7 @@ public:
{
switch (metaAttribute)
{
case MetaAttribute::SEMANTIC_TYPE: return "int";
case MetaAttribute::SEMANTIC_TYPE: return "Qty";
case MetaAttribute::PRESENCE: return "required";
default: return "";
}
@@ -4686,7 +4995,7 @@ public:
{
switch (metaAttribute)
{
case MetaAttribute::SEMANTIC_TYPE: return "int";
case MetaAttribute::SEMANTIC_TYPE: return "Qty";
case MetaAttribute::PRESENCE: return "required";
default: return "";
}
@@ -5043,6 +5352,15 @@ friend std::basic_ostream<CharT, Traits> & operator << (
builder << "{}";
}
builder << ", ";
builder << R"("MDTradeEntryID": )";
builder << +writer.mDTradeEntryID();
builder << ", ";
builder << R"("ExecRefID": )";
builder << '"' <<
writer.getExecRefIDAsJsonEscapedString().c_str() << '"';
builder << ", ";
{
bool atLeastOne = false;
Loaded 100 of 159 files, more files were not shown because too many files have changed in this diff. Show more