Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eadd6a0706 | ||
|
|
503a5e2715 | ||
|
|
786736b38f | ||
|
|
1ef2ed9d57 | ||
|
|
b39ea9528c | ||
|
|
c59ce773a7 | ||
|
|
400981738f | ||
|
|
3fd749dcd9 | ||
|
|
a250967286 | ||
|
|
a81126d888 | ||
|
|
c38ff5926e | ||
|
|
49df45574f | ||
|
|
7b2e19f7c8 | ||
|
|
c9e3cf8c67 | ||
|
|
6b31f7383c | ||
|
|
f2c95d6c28 | ||
|
|
1d3bbcc75f | ||
|
|
94bfdacf8b | ||
|
|
2a8d407f08 | ||
|
|
55ed962c63 | ||
|
|
68f766e00b | ||
|
|
c0252e06c5 | ||
|
|
4258bd4c82 | ||
|
|
4981d70bf2 | ||
|
|
b85c249a7d | ||
|
|
6721e07cb6 | ||
|
|
8aa5529218 | ||
|
|
ba5fff0168 | ||
|
|
976119fbee | ||
|
|
ed741c0621 | ||
|
|
e0cf3c739f | ||
|
|
59e2519a4d | ||
|
|
15de6f3e3d | ||
|
|
2e36f00382 | ||
|
|
9f24a3e7a2 | ||
|
|
e8dbf6fe09 | ||
|
|
aa403b83ef | ||
|
|
c507f20a8e | ||
|
|
ea52a4749d | ||
|
|
650710b5e2 | ||
|
|
20f6a29e05 | ||
|
|
e2f8d4db11 | ||
|
|
1812434ddc | ||
|
|
fd50050064 | ||
|
|
6ac43881ae | ||
|
|
4af1419748 | ||
|
|
7cfcdc3770 | ||
|
|
d03a705077 | ||
|
|
58eccad70d | ||
|
|
5f8ec04ff1 | ||
|
|
05e2107655 | ||
|
|
d41f613e32 | ||
|
|
bcbb598423 | ||
|
|
e88c44f9db | ||
|
|
93625957d1 |
No files matched your search
Vendored
+2
-2
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -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
|
||||
File renamed without changes.
@@ -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
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -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
|
||||
File renamed without changes.
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -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;
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace mbts.spark {
|
||||
|
||||
private bool _compute_midmarket {
|
||||
get {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+4
-1
@@ -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">
|
||||
|
||||
+16
-2
@@ -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) {
|
||||
|
||||
@@ -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 +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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
+326
-8
@@ -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
Reference in new issue
Block a user