Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9deb8cc237 | ||
|
|
11ff46a056 |
No files matched your search
@@ -1,10 +1,15 @@
|
||||
{
|
||||
"dockerFile": "Dockerfile",
|
||||
// Creates local dir to mount to HOME in docker
|
||||
"initializeCommand": "mkdir -p ${localEnv:HOME}/.bts/devcontainer-mounts/${devcontainerId}",
|
||||
"hostRequirements": {
|
||||
"memory": "8gb",
|
||||
"storage": "10gb"
|
||||
"memory": "4gb",
|
||||
"storage": "6gb"
|
||||
},
|
||||
"postCreateCommand": "/home/bts/spark/docker/sparkd/dev/devcontainer_post_create.sh",
|
||||
"containerUser": "bts",
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/home/bts/spark,type=bind",
|
||||
"workspaceFolder": "/home/bts/spark",
|
||||
"updateRemoteUserUID": true,
|
||||
"shutdownAction": "none",
|
||||
// Note: If running multiple containers on the same host, each must use a
|
||||
@@ -12,7 +17,7 @@
|
||||
// example, if 8000-8003 is taken, go with 9000-9003, then 10000-10003, etc.
|
||||
"runArgs": [
|
||||
"--shm-size",
|
||||
"8gb",
|
||||
"2gb",
|
||||
"-p",
|
||||
"8000:8000",
|
||||
"-p",
|
||||
@@ -24,15 +29,22 @@
|
||||
"-p",
|
||||
"12345:12345"
|
||||
],
|
||||
"mounts": [
|
||||
"source=${localEnv:HOME}/.bts/devcontainer-mounts/${devcontainerId},target=/home/bts,type=bind"
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"clangd.arguments": [
|
||||
"--compile-commands-dir=${workspaceFolder}/build/clang/debug"
|
||||
]
|
||||
},
|
||||
"extensions": [
|
||||
"eamodio.gitlens",
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
"ms-vscode.cmake-tools",
|
||||
"ms-vscode.cpptools",
|
||||
"xaver.clang-format"
|
||||
"ms-vscode.cpptools"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-65
@@ -55,21 +55,6 @@
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
},
|
||||
{
|
||||
"name": "spreader tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[spreader]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
},
|
||||
{
|
||||
"name": "DataStreamQueryAnalyzer",
|
||||
"type": "coreclr",
|
||||
@@ -123,18 +108,6 @@
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Spark (Skip Build)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"windows": {
|
||||
"program": "Spark.exe",
|
||||
"cwd": "build/Debug/bin/net7.0-windows",
|
||||
},
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Spark (Parameterized)",
|
||||
"type": "coreclr",
|
||||
@@ -152,39 +125,6 @@
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Spark Test",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"windows": {
|
||||
"preLaunchTask": "Build Spark (Debug)",
|
||||
"program": "Spark.Console.exe",
|
||||
"cwd": "build/Debug/bin/net7.0",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkTest}"
|
||||
]
|
||||
},
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Spark Test (Skip Build)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"windows": {
|
||||
"program": "Spark.Console.exe",
|
||||
"cwd": "build/Debug/bin/net7.0",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkTest}"
|
||||
]
|
||||
},
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Sparkd",
|
||||
"type": "cppdbg",
|
||||
@@ -315,11 +255,6 @@
|
||||
"description": "Config Server URL",
|
||||
"default": "http://localhost:8000/config",
|
||||
},
|
||||
{
|
||||
"id": "sparkTest",
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
{
|
||||
"id": "sparkdTest",
|
||||
"type": "promptString",
|
||||
|
||||
Vendored
+1
-103
@@ -641,21 +641,6 @@
|
||||
"color": "terminal.ansiGreen",
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Spark (Skip Build)",
|
||||
"type": "process",
|
||||
"command": "",
|
||||
"windows": {
|
||||
"command": "Spark.exe",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net7.0-windows"
|
||||
},
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
"color": "terminal.ansiGreen",
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Spark Config Query",
|
||||
"dependsOn": "Build Spark",
|
||||
@@ -699,24 +684,6 @@
|
||||
"color": "terminal.ansiGreen",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Run Sparkd (Skip Build)",
|
||||
"type": "process",
|
||||
"command": "",
|
||||
"linux": {
|
||||
"command": "sparkd",
|
||||
},
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
"color": "terminal.ansiGreen",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Run Sparkd Config Query",
|
||||
"dependsOn": "Build Sparkd",
|
||||
@@ -738,65 +705,6 @@
|
||||
"color": "terminal.ansiGreen",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Test Spark (Debug)",
|
||||
"dependsOn": "Build Spark (Debug)",
|
||||
"type": "process",
|
||||
"icon": {
|
||||
"id": "coverage",
|
||||
"color": "terminal.ansiYellow",
|
||||
},
|
||||
"command": "",
|
||||
"windows": {
|
||||
"command": "Spark.Console.exe",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkTest}"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Test Sparkd",
|
||||
"dependsOn": "Build Sparkd",
|
||||
"type": "process",
|
||||
"command": "",
|
||||
"linux": {
|
||||
"command": "sparkd",
|
||||
},
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkdTest}",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
|
||||
},
|
||||
"icon": {
|
||||
"id": "coverage",
|
||||
"color": "terminal.ansiYellow",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Test Sparkd (Skip Build)",
|
||||
"type": "process",
|
||||
"command": "",
|
||||
"linux": {
|
||||
"command": "sparkd",
|
||||
},
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkdTest}",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
|
||||
},
|
||||
"icon": {
|
||||
"id": "coverage",
|
||||
"color": "terminal.ansiYellow",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "View DataStreamQueryAnalyzer Logs (Last Run)",
|
||||
"type": "process",
|
||||
@@ -915,16 +823,6 @@
|
||||
"id": "sparkdConfigQuery",
|
||||
"type": "promptString",
|
||||
"description": "Config Type(s)"
|
||||
},
|
||||
{
|
||||
"id": "sparkTest",
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
{
|
||||
"id": "sparkdTest",
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -117,6 +117,7 @@ else()
|
||||
# subdirs
|
||||
if (BUILD_SPARKD)
|
||||
add_subdirectory(src/lib_binance_execution)
|
||||
add_subdirectory(src/lib_deribit_emumba)
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/data_files)
|
||||
endif()
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"CMAKE_CXX_FLAGS_DEBUG": "$env{SPARK_CMAKE_CXX_FLAGS} /MTd",
|
||||
"CMAKE_CXX_FLAGS_RELEASE": "$env{SPARK_CMAKE_CXX_FLAGS} /MT",
|
||||
"CMAKE_CXX_STANDARD": "20",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_DEBUG": "/DEBUG",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_RELEASE": "/OPT:REF /OPT:ICF /LTCG",
|
||||
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
include(ExternalProject)
|
||||
|
||||
if(NOT TARGET websocket-library_project)
|
||||
ExternalProject_Add(websocket-library_project
|
||||
GIT_REPOSITORY https://fintech.websocket:h_ubh-4dsWRB569wwKyg@gitlab.com/cryptoexchanges1/websocket-client-using-epoll.git
|
||||
GIT_TAG enhancement/config-ssl
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/websocket-library
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DLOG_LEVEL=INFO
|
||||
-DENABLE_ENCRYPTION=True
|
||||
)
|
||||
endif()
|
||||
|
||||
ExternalProject_Get_Property(websocket-library_project BINARY_DIR)
|
||||
ExternalProject_Get_Property(websocket-library_project SOURCE_DIR)
|
||||
|
||||
set(websocket-library_INCLUDE_DIR ${SOURCE_DIR}/include)
|
||||
set(websocket-library_LIB_DIR ${BINARY_DIR}/src)
|
||||
|
||||
add_library(websocket-library STATIC IMPORTED)
|
||||
|
||||
set_property(TARGET websocket-library PROPERTY IMPORTED_LOCATION ${websocket-library_LIB_DIR}/libemumba-websocket-library.a)
|
||||
add_dependencies(websocket-library websocket-library_project)
|
||||
install(FILES ${websocket-library_LIB_DIR}/libemumba-websocket-library.a DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
Submodule
+1
Submodule docker/linux added at 56c61231f3.
Submodule
+1
Submodule docker/windows added at de7d40c09d.
+1
-1
Submodule externals updated: 959b63f2f6...8069cc893f.
@@ -4,7 +4,7 @@ namespace bts.SilverPine {
|
||||
using BCG;
|
||||
|
||||
using System;
|
||||
[DataStreamUserRespresentation("DateTimeExpanderStream")]
|
||||
|
||||
public class DateTimeExpanstionStream : ViewStream {
|
||||
public DateTimeExpanstionStream(ViewStream parent_stream, string inp_col) {
|
||||
Func<IBlueTableSchema, IBlueTableSchema> get_schema = (s) => {
|
||||
@@ -24,11 +24,11 @@ namespace bts.SilverPine {
|
||||
IBlueTableSchema s = get_schema(r.schema);
|
||||
MutableBlueRow rv = new MutableBlueRow(s, r);
|
||||
rv.setValue($"{inp_col}_date", dt.Date);
|
||||
rv.setValue($"{inp_col}_day_of_week", dt.DayOfWeek.ToString());
|
||||
rv.setValue($"{inp_col}_day", dt.DayOfWeek.ToString());
|
||||
rv.setValue($"{inp_col}_day_of_month", dt.Day);
|
||||
rv.setValue($"{inp_col}_month", dt.Date.Month);
|
||||
rv.setValue($"{inp_col}_hour", dt.Hour);
|
||||
rv.setValue($"{inp_col}_minute", dt.Minute);
|
||||
rv.setValue($"{inp_col}_min", dt.Minute);
|
||||
rv.setValue($"{inp_col}_seconds_of_day", (dt - dt.Date).TotalSeconds);
|
||||
return rv;
|
||||
});
|
||||
|
||||
@@ -36,8 +36,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnExpressions", "Bt
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnSortExpressions", "Bts.ColumnSortExpressions\Bts.ColumnSortExpressions.csproj", "{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStreamQueryAnalyzer", "DataStreamQueryAnalyzer\DataStreamQueryAnalyzer.csproj", "{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -115,9 +113,5 @@ Global
|
||||
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -156,14 +156,6 @@ namespace bts.SilverPine {
|
||||
ViewStream rs = new FutureHistoricalBarsStream(fut_contract, bar_size.ToString(), dt_range_beg, dt_range_end);
|
||||
mySetRealSource(rs);
|
||||
}
|
||||
public FutureHistoricalBarsStream(string fut_contract, string bar_size)
|
||||
: this(fut_contract, bar_size, DateTime.MinValue, DateTime.MaxValue) { }
|
||||
|
||||
public FutureHistoricalBarsStream(string fut_contract, string bar_size,
|
||||
string dt_range_beg, string dt_range_end)
|
||||
: this(fut_contract, bar_size, DateTime.Parse(dt_range_beg),
|
||||
DateTime.Parse(dt_range_end)) { }
|
||||
|
||||
public FutureHistoricalBarsStream(string fut_contract, string bar_size,
|
||||
DateTime dt_range_beg, DateTime dt_range_end)
|
||||
:
|
||||
|
||||
@@ -8,7 +8,6 @@ using MathNet.Numerics.Providers.FourierTransform;
|
||||
using BCG;
|
||||
using System.Windows.Automation;
|
||||
using bts.collections;
|
||||
using mbts.co2;
|
||||
|
||||
namespace mbts.spark {
|
||||
// Publishes theos for all options (on the side, also publishes the theos for TC2).
|
||||
@@ -115,53 +114,11 @@ namespace mbts.spark {
|
||||
}
|
||||
sym_theos.set_data(k, tp);
|
||||
}
|
||||
_override_theos(ref rv);
|
||||
foreach (var kv in rv) {
|
||||
_bb.post(SparkTags.TC2Tags.LiveTheosTag(kv.Key), kv.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void _override_theos(ref Dictionary<(string, Venue), ExpStrikeKeyedData<TheoPoint>> theos) {
|
||||
if (_compute_midmarket) {
|
||||
SystemContractData sc_data = _bb.read_state(SparkTags.SystemContractsTag);
|
||||
foreach (var kv in theos) {
|
||||
string sym = kv.Key.Item1;
|
||||
Venue exchange = kv.Key.Item2;
|
||||
foreach (var t in kv.Value.get_data_linearly()) {
|
||||
DateTime exp = t.first;
|
||||
double strike = t.second;
|
||||
TheoPoint tp = t.third;
|
||||
var call_sec_md = sc_data.get_option(exchange, sym, exp, strike, PutOrCall.Call);
|
||||
{
|
||||
NativePriceSize call_bid, call_ask;
|
||||
ulong time = 0;
|
||||
call_sec_md.Value.md.get_bbo(ref time, out call_bid, out call_ask);
|
||||
double call_mid = (call_bid.price + call_ask.price) / 2;
|
||||
tp.call = call_mid;
|
||||
}
|
||||
{
|
||||
var put_sec_md = sc_data.get_option(exchange, sym, exp, strike, PutOrCall.Put);
|
||||
NativePriceSize put_bid, put_ask;
|
||||
ulong time = 0;
|
||||
put_sec_md.Value.md.get_bbo(ref time, out put_bid, out put_ask);
|
||||
double put_mid = (put_bid.price + put_ask.price) / 2;
|
||||
tp.put = put_mid;
|
||||
}
|
||||
}
|
||||
}
|
||||
//replace theos with market bid
|
||||
// get it from the bus and override
|
||||
} else {
|
||||
// do nothing, passes unchanged
|
||||
}
|
||||
}
|
||||
|
||||
private bool _compute_midmarket {
|
||||
get {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void _interp_and_publish() {
|
||||
var timer = Stopwatch.StartNew();
|
||||
|
||||
@@ -281,16 +281,5 @@ namespace mbts.spark {
|
||||
else
|
||||
return tp.put + (tp.pdelta * driver_diff) + (0.5 * tp.pgamma * driver_diff * driver_diff);
|
||||
}
|
||||
|
||||
public static TheoPoint extrapolate(TheoPoint tp, double driver, bool is_call) {
|
||||
TheoPoint rv = tp.Clone() as TheoPoint;
|
||||
double h = driver - tp.driver;
|
||||
if (is_call) {
|
||||
rv.call = tp.call + h * (tp.cdelta);
|
||||
} else {
|
||||
rv.put = tp.put + h * (tp.cdelta);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace mbts.analytics {
|
||||
// prices (specified in the vs settings).
|
||||
// It has public setters that get
|
||||
// 1. contracts
|
||||
// 2. vs input
|
||||
// 2. vs inout
|
||||
// 3. exp params(drivers, interst rate etc)
|
||||
// 4. driver prices
|
||||
// 5. vol time
|
||||
|
||||
@@ -133,30 +133,16 @@ namespace bts {
|
||||
|
||||
// set 'unit quantity' for each sec
|
||||
foreach (var smd in exch_secs.Values) {
|
||||
var (priceDisplay, qtyDisplay) = GetDisplaySettings(smd.security);
|
||||
exch_secs[smd.id] = new(smd.security, smd.md, priceDisplay, qtyDisplay);
|
||||
}
|
||||
_bus.post(SparkTags.ServerSecuritiesTag, exch_secs);
|
||||
}
|
||||
|
||||
(TickDisplaySettingsImmutable? Price, TickDisplaySettingsImmutable? Quantity) GetDisplaySettings(SecurityImmutable sec) {
|
||||
var meta = _security_metadata.get(sec);
|
||||
var priceDisplay = meta?.PriceDisplay ?? Security.Defaults.price_display_settingsi;
|
||||
var qtyDisplay = meta?.QuantityDisplay ?? Security.Defaults.quantity_display_settingsi;
|
||||
if (sec.HasContractSpec) {
|
||||
var pd = priceDisplay.CloneToMutable();
|
||||
pd.TickFactors = TickUtils.get_tick(sec, 0).to_api_tick_factors();
|
||||
priceDisplay = new(pd);
|
||||
if (sec.ContractSpec.QuantityInfos.TryGetFirst(out var qtyInfo)) {
|
||||
var qd = qtyDisplay.CloneToMutable();
|
||||
qd.ShowDecimalPart = qtyInfo.TickBase.is_real_non_zero() && !(qtyInfo.TickUnit / qtyInfo.TickBase).is_int();
|
||||
qd.TickFactors ??= new();
|
||||
qd.TickFactors.TickBase = qtyInfo.TickBase;
|
||||
qd.TickFactors.TickUnit = qtyInfo.TickUnit;
|
||||
qtyDisplay = new(qd);
|
||||
var meta = _security_metadata.get(smd.security);
|
||||
if (meta.HasValue) {
|
||||
exch_secs[smd.id] = new(
|
||||
smd.security,
|
||||
smd.md,
|
||||
meta.Value.HasPriceDisplay ? meta.Value.PriceDisplay : null,
|
||||
meta.Value.HasQuantityDisplay ? meta.Value.QuantityDisplay : null);
|
||||
}
|
||||
}
|
||||
return (priceDisplay, qtyDisplay);
|
||||
_bus.post(SparkTags.ServerSecuritiesTag, exch_secs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace mbts.spark {
|
||||
public enum MarketViewColumnType {
|
||||
None,
|
||||
Venue,
|
||||
Security,
|
||||
Volume,
|
||||
Last,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
namespace bts.shell.market_view {
|
||||
using bts.api;
|
||||
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
|
||||
@@ -10,15 +8,8 @@ namespace bts.shell.market_view {
|
||||
|
||||
[ProtoContract]
|
||||
class MarketViewFilterStates {
|
||||
[ProtoMember(37)]
|
||||
public Venue[] venue { get; set; }
|
||||
|
||||
[ProtoMember(38)]
|
||||
public string[] currency { get; set; }
|
||||
|
||||
[ProtoMember(1)]
|
||||
public string security { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public DoubleMinMaxState bid_price { get; set; }
|
||||
|
||||
@@ -132,8 +123,6 @@ namespace bts.shell.market_view {
|
||||
public Func<MarketViewVM.Row, bool> build_predicate() {
|
||||
var pb = new ColumnPredicateBuilder<MarketViewVM.Row>();
|
||||
// security
|
||||
pb.add_discrete_values(venue, r => r.venue);
|
||||
pb.add_discrete_values(currency, r => r.currency);
|
||||
pb.add_regex(security, r => r.security.ToString());
|
||||
pb.add_regex(security_key, r => r.security_key.ToString());
|
||||
|
||||
|
||||
@@ -206,7 +206,6 @@
|
||||
// security
|
||||
public SecurityMD security_md { get; }
|
||||
public SecurityImmutable security => security_md.security;
|
||||
public bts.api.Venue venue => security_md.security.Exchange;
|
||||
public uint security_key => security_md.security.Id;
|
||||
public string currency => security_md.security.Currency;
|
||||
|
||||
|
||||
@@ -92,7 +92,6 @@ namespace bts.shell.market_view {
|
||||
}
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings venue => _mk(Columns.Venue, ColHeader.Exchange, ColWidth.Exchange, h_align: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings security => _mk(Columns.Security, ColHeader.Security, ColWidth.Security, h_align: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings currency => _mk(Columns.Currency, ColHeader.Currency, ColWidth.Currency, h_align: HorizontalAlignment.Left, is_visible: false);
|
||||
public static GridColumnSettings volume => _mk(Columns.Volume, ColHeader.Volume, ColWidth.Volume, h_align: HorizontalAlignment.Left);
|
||||
|
||||
@@ -98,14 +98,7 @@ namespace mbts.spark {
|
||||
_sec_meta_store = sec_meta_store;
|
||||
_settings_hub = settings_hub;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
currency_filter = DiscreteValuesFilterVM.Create<Row, string>(
|
||||
bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.Sample(TimeSpan.FromSeconds(1))
|
||||
.Select(sec_mds => sec_mds.Values.Select(smd => smd.security.Currency).Distinct().ToList()),
|
||||
r => r.currency,
|
||||
nameof(Row.currency),
|
||||
EqualityComparer<string>.Default,
|
||||
Comparer<string>.Default);
|
||||
currency_filter =new RegexFilterVM<Row>(r => r.currency, nameof(Row.currency));
|
||||
// rows
|
||||
_wrapped_rows = _rows.WrapSelector(r => r.security_md, s => {
|
||||
var r = new Row(_bus, s, _orders_model, _position_model);
|
||||
@@ -129,9 +122,8 @@ namespace mbts.spark {
|
||||
const double working_orders_col_width = 105;
|
||||
var price_converter = new TickDisplayConverter();
|
||||
|
||||
add_col(ColKey.Venue, r => r.venue.ToString(), nameof(Row.venue), venue_filter, FilterType.DiscreteValues, ColWidth.Exchange);
|
||||
add_col(ColKey.Security, r => r.security.ToString(), nameof(Row.security), security_filter, FilterType.Regex, ColWidth.Security);
|
||||
add_col(ColKey.Currency, r => r.currency, nameof(Row.currency), currency_filter, FilterType.DiscreteValues, ColWidth.Currency);
|
||||
add_col(ColKey.Currency, r => r.currency, nameof(Row.currency), currency_filter, FilterType.Regex, ColWidth.Currency);
|
||||
add_col(ColKey.Volume, r => r.volume_text, nameof(Row.volume), volume_filter, FilterType.ULongMinMax, ColWidth.Volume);
|
||||
|
||||
// last
|
||||
@@ -396,9 +388,8 @@ namespace mbts.spark {
|
||||
internal IBus bus => _bus;
|
||||
internal OrdersModel orders_model => _orders_model;
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, Venue[]> venue_filter { get; } = DiscreteValuesFilterVM.Common.exchange<Row>(r => r.venue, nameof(Row.venue));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, string[]> currency_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, string> currency_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_qty_filter { get; } = new DoubleMinMax<Row>(r => r.bid_qty_display, nameof(Row.bid_qty_display));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> dbid_qty_filter { get; } = new DoubleMinMax<Row>(r => r.dbid_qty_display, nameof(Row.dbid_qty_display));
|
||||
@@ -544,8 +535,6 @@ namespace mbts.spark {
|
||||
|
||||
public MarketViewFilterStates get_filter_states() =>
|
||||
new() {
|
||||
venue = venue_filter.get_state(),
|
||||
currency = currency_filter.get_state(),
|
||||
security = security_filter.get_state(),
|
||||
bid_price = bid_price_filter.get_state(),
|
||||
bid_merged_size = bid_qty_filter.get_state(),
|
||||
@@ -591,8 +580,6 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
using var _ = rows_view.DeferRefresh();
|
||||
venue_filter.load_state(filters.venue);
|
||||
currency_filter.load_state(filters.currency);
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_qty_filter.load_state(filters.bid_merged_size);
|
||||
|
||||
@@ -237,17 +237,6 @@ namespace mbts.spark {
|
||||
DefaultColumnWidths.Qty,
|
||||
get_fg: r => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
|
||||
|
||||
add_col(
|
||||
Columns.NotionalValue,
|
||||
DefaultColumnHeaders.NotionalValue,
|
||||
r => _get_row(r).security.spec_or_default().format(r.fill.notional_value()),
|
||||
new DoubleMinMax<FillTuple>(r => r.fill.notional_value()),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
fs => fs.notional_value,
|
||||
(fs, cf) => fs.notional_value = cf,
|
||||
DefaultColumnWidths.NotionalValue,
|
||||
get_fg: r => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
|
||||
|
||||
add_col(
|
||||
Columns.Price,
|
||||
DefaultColumnHeaders.Price,
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace mbts.ui.spark {
|
||||
Side,
|
||||
Price,
|
||||
Size,
|
||||
NotionalValue,
|
||||
FillKind,
|
||||
FillStatus,
|
||||
FillKey,
|
||||
|
||||
@@ -61,10 +61,6 @@ namespace bts.shell.fills {
|
||||
[DIYGridColumnID(nameof(Columns.Size))]
|
||||
public DoubleMinMaxState size { get; set; }
|
||||
|
||||
[ProtoMember(18)]
|
||||
[DIYGridColumnID(nameof(Columns.NotionalValue))]
|
||||
public DoubleMinMaxState notional_value { get; set; }
|
||||
|
||||
[ProtoMember(11)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKey))]
|
||||
public string fill_key { get; set; }
|
||||
@@ -117,7 +113,6 @@ namespace bts.shell.fills {
|
||||
pb.add_discrete_values(fs.fill_status, ft => ft.fill.FillStatus);
|
||||
// numeric
|
||||
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
|
||||
pb.add_numeric(fs.notional_value, ft => ft.fill.notional_value());
|
||||
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
|
||||
|
||||
if (fs.exp_date.HasValue) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -66,12 +66,8 @@ namespace mbts.spark {
|
||||
_orders = orders;
|
||||
_real_price = real_price;
|
||||
_text_lines = backing(nameof(text_lines), () => {
|
||||
var tf = _owner.model.security.tick_factors();
|
||||
var fmt = MDDisplayHelper.get_value_formatter(
|
||||
owner.current_settings.size_display,
|
||||
_owner.model.security_md.quantity_display,
|
||||
tf.to_api_tick_display_settingsi());
|
||||
return BadgeRender.format(side, _quantity, _filled, price, _current_badge_style, fmt);
|
||||
var fmt = MDDisplayHelper.get_value_formatter(_owner.model.security_md.quantity_display);
|
||||
return BadgeRender.format(side, _quantity, _filled, _current_badge_style, fmt);
|
||||
});
|
||||
_clear_phantom_price = new(() => {
|
||||
_phantom_price = double.NaN;
|
||||
|
||||
@@ -100,9 +100,6 @@ namespace mbts.spark {
|
||||
readonly OrderEntryPanelItemVisibilityVM _cancel_all_visibility = new("Cancel All");
|
||||
readonly OrderEntryPanelItemVisibilityVM _status_message_visibility = new("Status Message");
|
||||
|
||||
readonly PanelPricePointVM _bid_price = new("Bid");
|
||||
readonly PanelPricePointVM _ask_price = new("Ask");
|
||||
readonly PanelPricePointVM _mid_price = new("Mid");
|
||||
readonly PanelRelativePricePointVM _day_change = new("Day Change");
|
||||
readonly PanelPricePointVM _session_high = new("High");
|
||||
readonly PanelPricePointVM _session_low = new("Low");
|
||||
@@ -142,6 +139,9 @@ namespace mbts.spark {
|
||||
accounts_view = new ListCollectionView<AccountTuple?>(_accounts) {
|
||||
CustomSort = NullCheckComparer<AccountTuple>.Default
|
||||
};
|
||||
// cmds
|
||||
tick_up_ticks_per_row_cmd = new DelegateCommand(increment_ticks_per_row);
|
||||
tick_down_ticks_per_row_cmd = new DelegateCommand(decrement_ticks_per_row);
|
||||
// update
|
||||
_update = new DeferrableAction(() => result_changed?.Invoke());
|
||||
mini_ob.result_changed += _update.Invoke;
|
||||
@@ -191,6 +191,26 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data);
|
||||
void increment_ticks_per_row() {
|
||||
var tpr = ticks_per_row;
|
||||
for (uint i = 1; i < 100; i++) {
|
||||
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security, ticks_per_row + i);
|
||||
if (new_tpr != tpr) {
|
||||
ticks_per_row = new_tpr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void decrement_ticks_per_row() {
|
||||
var tpr = ticks_per_row;
|
||||
for (uint i = 1; i < tpr; i++) {
|
||||
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security, tpr - i);
|
||||
if (new_tpr != tpr) {
|
||||
ticks_per_row = new_tpr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
@@ -215,8 +235,15 @@ namespace mbts.spark {
|
||||
}
|
||||
public uint ticks_per_row {
|
||||
get => _ticks_per_row;
|
||||
set => Set(ref _ticks_per_row, value);
|
||||
set => Set(ref _ticks_per_row, value, dependent_props: nameof(rounded_ticks_per_row));
|
||||
}
|
||||
public ICommand tick_up_ticks_per_row_cmd { get; }
|
||||
public ICommand tick_down_ticks_per_row_cmd { get; }
|
||||
// since settings profiles may be shared among ladders
|
||||
// of different securities/tick sizes, the ticks per row setting
|
||||
// may need to be rounded to a number that is a factor of $1.00
|
||||
public uint? rounded_ticks_per_row =>
|
||||
TickUtils.fit_ticks_per_row_to_dollars(_security, ticks_per_row);
|
||||
|
||||
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
|
||||
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
|
||||
@@ -300,12 +327,7 @@ namespace mbts.spark {
|
||||
|
||||
public IEnumerable order_entry_panel_items {
|
||||
get {
|
||||
// Order these items according to their display order in the actual ladder (from top to
|
||||
// bottom). There are a lot of them, so this is a simple convenience for the user.
|
||||
yield return _zoom_slider_visbility;
|
||||
yield return _ask_price;
|
||||
yield return _mid_price;
|
||||
yield return _bid_price;
|
||||
yield return _day_change;
|
||||
yield return _session_high;
|
||||
yield return _session_low;
|
||||
@@ -428,12 +450,6 @@ namespace mbts.spark {
|
||||
zoom_factor = settings.zoom_factor;
|
||||
// left-panel (show/hide)
|
||||
_zoom_slider_visbility.show = settings.zoom_slider_visible;
|
||||
_bid_price.show = settings.bid_price_visible;
|
||||
_bid_price.load_from(settings.panel_price_point_settings.bid_price);
|
||||
_ask_price.show = settings.ask_price_visible;
|
||||
_ask_price.load_from(settings.panel_price_point_settings.ask_price);
|
||||
_mid_price.show = settings.mid_price_visible;
|
||||
_mid_price.load_from(settings.panel_price_point_settings.mid_price);
|
||||
_day_change.show = settings.day_visible;
|
||||
_day_change.load_from(settings.panel_price_point_settings.day_change);
|
||||
_session_high.show = settings.high_visible;
|
||||
@@ -521,9 +537,6 @@ namespace mbts.spark {
|
||||
.ToList();
|
||||
// left-panel price items
|
||||
clone.panel_price_point_settings = new LadderPanelPricePointSettings {
|
||||
bid_price = _bid_price.create(),
|
||||
ask_price = _ask_price.create(),
|
||||
mid_price = _mid_price.create(),
|
||||
day_change = _day_change.create(),
|
||||
open = _open.create(),
|
||||
session_low = _session_low.create(),
|
||||
@@ -548,9 +561,6 @@ namespace mbts.spark {
|
||||
clone.order_entry_panel_initial_width = _order_entry_panel_width;
|
||||
clone.zoom_factor = zoom_factor;
|
||||
// left-panel (components)
|
||||
clone.bid_price_visible = _bid_price.show;
|
||||
clone.ask_price_visible = _ask_price.show;
|
||||
clone.mid_price_visible = _mid_price.show;
|
||||
clone.day_visible = _day_change.show;
|
||||
clone.high_visible = _session_high.show;
|
||||
clone.low_visible = _session_low.show;
|
||||
@@ -705,7 +715,6 @@ namespace mbts.spark {
|
||||
|
||||
public uint sample_quantity { get; } = 50;
|
||||
public uint sample_filled { get; } = 12;
|
||||
public double sample_price => 2175.50;
|
||||
public Side sample_side { get; } = Side.Buy;
|
||||
public Brush badge_border { get; } = new SolidColorBrush(BTSColors.MarketData.ask);
|
||||
public Brush badge_background { get; } = Brushes.White;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -14,9 +14,9 @@ using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
|
||||
using Spark.Core.Disposables;
|
||||
using Spark.Core.Math;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@@ -37,6 +37,9 @@ namespace mbts.spark {
|
||||
partial class LadderGridHost : Control {
|
||||
static readonly int _initial_row_count;
|
||||
static readonly bool _show_scrollbar;
|
||||
static readonly bool _lighten_market_cells;
|
||||
static readonly Color? _bid_market_cell_background;
|
||||
static readonly Color? _ask_market_cell_background;
|
||||
|
||||
readonly VisualHost _visual_host = new();
|
||||
readonly LadderOrdersHost _ordershost;
|
||||
@@ -80,7 +83,6 @@ namespace mbts.spark {
|
||||
LadderTactileAdorner _tactile_adorner;
|
||||
ulong _prices_updated;
|
||||
NativePriceSize[] _bids = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH], _asks = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
|
||||
SecurityMDExtensions.Book _depth = SecurityMDExtensions.Book.Empty;
|
||||
bool _price_dirty = false, _piq_dirty = false;
|
||||
LadderPriceList _price_list = new() {
|
||||
length = _initial_row_count,
|
||||
@@ -91,10 +93,8 @@ namespace mbts.spark {
|
||||
readonly MarketBreakLines _mkt_break_lines;
|
||||
NativePriceSize _last_trade;
|
||||
Func<NativePriceSize, string> _fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(
|
||||
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
|
||||
Func<double, double, string> _fmt_mkt_quantity_impl = (v, p) => v.ToString();
|
||||
Func<LadderPriceLevel, string> _fmt_price_level_impl = MDDisplayHelper.get_formatter_lpl(
|
||||
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
|
||||
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi);
|
||||
Func<double, string> _fmt_mkt_quantity_impl = v => v.ToString();
|
||||
int _old_tooltip_row = int.MaxValue;
|
||||
NativePriceSize? _old_last_trade;
|
||||
string _direction_arrow = string.Empty;
|
||||
@@ -106,7 +106,17 @@ namespace mbts.spark {
|
||||
static LadderGridHost() {
|
||||
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
|
||||
_show_scrollbar = knobs.ShowScrollbar;
|
||||
_lighten_market_cells = knobs.LightenMarketCells;
|
||||
_initial_row_count = (int)knobs.ScrollableTickWindowSize;
|
||||
// For market cell backgrounds, 0 means null. Note that the full
|
||||
// range of colors can still be expressed, as zero is merely one of
|
||||
// 2^24 possible representations of 'transparent.'
|
||||
if (knobs.BidMarketCellBackground != 0) {
|
||||
_bid_market_cell_background = BTSColors.Ladder.bid_column_background;
|
||||
}
|
||||
if (knobs.AskMarketCellBackground != 0) {
|
||||
_ask_market_cell_background = BTSColors.Ladder.ask_column_background;
|
||||
}
|
||||
}
|
||||
|
||||
public LadderGridHost() {
|
||||
@@ -726,8 +736,8 @@ namespace mbts.spark {
|
||||
};
|
||||
view.RequestClose += (o, _) => popup.IsOpen = false;
|
||||
}
|
||||
string fmt_mbo_qty(double value, double price) =>
|
||||
value < 0 ? $"-{_fmt_mkt_quantity(value, price)}" : _fmt_mkt_quantity(value, price);
|
||||
string fmt_mbo_qty(int value) =>
|
||||
value < 0 ? $"-{_fmt_mkt_quantity((uint)value)}" : _fmt_mkt_quantity((uint)value);
|
||||
}
|
||||
|
||||
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
|
||||
@@ -861,13 +871,32 @@ namespace mbts.spark {
|
||||
switch (column.column_type) {
|
||||
case ColumnType.Order: _ordershost.set_col_width(column.settings.width); break;
|
||||
case ColumnType.Bid:
|
||||
render_market_col(_depth.Bids);
|
||||
render_market_col(_bids, _bid_market_cell_background);
|
||||
break;
|
||||
case ColumnType.Price:
|
||||
_refresh_prices(column);
|
||||
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
|
||||
LadderRender.render_prices(
|
||||
column.visual_tup.visual,
|
||||
column.visual_tup.glyph_context,
|
||||
_price_list,
|
||||
_settings.row_height,
|
||||
column.settings,
|
||||
_spec,
|
||||
_vm.md.settlement_price,
|
||||
_settings.net_change == LadderSettings.NetChangeTerms.Ticks);
|
||||
} else {
|
||||
LadderRender.render_prices(
|
||||
column.visual_tup.visual,
|
||||
column.visual_tup.glyph_context,
|
||||
_price_list,
|
||||
_settings.row_height,
|
||||
column.settings,
|
||||
_spec,
|
||||
_settings.price_display_format);
|
||||
}
|
||||
break;
|
||||
case ColumnType.Ask:
|
||||
render_market_col(_depth.Asks);
|
||||
render_market_col(_asks, _ask_market_cell_background);
|
||||
break;
|
||||
case ColumnType.Volume:
|
||||
_volume_container.mark_dirty();
|
||||
@@ -880,9 +909,9 @@ namespace mbts.spark {
|
||||
if (_mkt_break_lines.high_render_index.HasValue)
|
||||
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index.Value, _settings.session_high_line.color);
|
||||
}
|
||||
void render_market_col(IEnumerable<(double Price, double Volume, double NotionalValue)> depth) {
|
||||
void render_market_col(NativePriceSize[] depth, Color? market_cell_background) {
|
||||
var mid = LadderRender.index_of(_price_list, _last_origin_price, _price_comparer);
|
||||
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_price_level);
|
||||
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_mkt_depth_item, _lighten_market_cells, market_cell_background);
|
||||
}
|
||||
}
|
||||
_apply_column_visual_offset();
|
||||
@@ -928,7 +957,6 @@ namespace mbts.spark {
|
||||
// **use local vm to protect against re-assignment,
|
||||
// so the events are unhooked from the same instance to which they were added
|
||||
// when the subs are disposed.
|
||||
Disposable.dispose(ref _vm_sub);
|
||||
var local_vm = _vm;
|
||||
local_vm.on_tick += _tick;
|
||||
local_vm.request_recenter += on_request_recenter;
|
||||
@@ -987,10 +1015,8 @@ namespace mbts.spark {
|
||||
//System.Diagnostics.Debug.WriteLine(" Tooltip - Previous tooltip is visible, but we've changed rows");
|
||||
_hide_tool_tip();
|
||||
}
|
||||
double price = row < 0 || row >= (_price_list?.Count ?? 0)
|
||||
? double.NaN
|
||||
: _price_for_row(row);
|
||||
_tooltip_view.Content = _fmt_mkt_quantity(volume, price);
|
||||
|
||||
_tooltip_view.Content = _fmt_mkt_quantity(volume);
|
||||
_tooltip_view.IsOpen = true;
|
||||
ToolTip = _tooltip_view;
|
||||
} else {
|
||||
@@ -1077,34 +1103,19 @@ namespace mbts.spark {
|
||||
return Disposable.Create(() => _can_user_send_orders_changed -= o.OnNext);
|
||||
});
|
||||
|
||||
LadderPriceList _create_price_list() {
|
||||
if (_settings is not null) {
|
||||
switch (_settings.net_change) {
|
||||
case LadderSettings.NetChangeTerms.BasisPoints:
|
||||
case LadderSettings.NetChangeTerms.NetBasisPoints:
|
||||
return new() {
|
||||
length = _initial_row_count,
|
||||
regional_tick = new RegionalTickFactors(new user.settings.TickFactors(10000, 1)),
|
||||
};
|
||||
}
|
||||
}
|
||||
return new() {
|
||||
length = _initial_row_count,
|
||||
regional_tick = _spec
|
||||
};
|
||||
}
|
||||
|
||||
#region Timer.Tick, Refresh Methods
|
||||
void _tick() {
|
||||
if (_settings != null) {
|
||||
ulong unused = 0;
|
||||
bool last_trade_changed = _vm.md.get_last_trade(ref unused, out _last_trade, out DateTime _, out ulong __);
|
||||
bool depth_changed = _vm.md.get_depth(ref _prices_updated, ref _bids, ref _asks, NativePriceHelpers.COCONUT_MAX_DEPTH);
|
||||
_depth = _vm.security_md.Depth(_bids, _asks, _vm.securities);
|
||||
_mkt_break_lines.refresh();
|
||||
if (_price_list is null) {
|
||||
Log.assert(_price_dirty);
|
||||
_price_list = _create_price_list();
|
||||
_price_list = new LadderPriceList {
|
||||
length = _initial_row_count,
|
||||
security = security
|
||||
};
|
||||
var mid_tick = security.tickdiff(_mkt_break_lines.origin, 0) ?? 0;
|
||||
if (mid_tick > int.MaxValue) {
|
||||
Log.Ui.error(
|
||||
@@ -1141,8 +1152,10 @@ namespace mbts.spark {
|
||||
_scroll_viewer.ScrollToVerticalOffset(new_vertical_offset);
|
||||
}
|
||||
}
|
||||
_refresh_prices();
|
||||
} else {
|
||||
_refresh_prices();
|
||||
}
|
||||
_refresh_prices();
|
||||
scroll_to_pending_target();
|
||||
_refresh_all_visuals();
|
||||
} else {
|
||||
@@ -1151,17 +1164,8 @@ namespace mbts.spark {
|
||||
_refresh_bid_mkt_break();
|
||||
if (_mkt_break_lines.ask_dirty)
|
||||
_refresh_ask_mkt_break();
|
||||
if (_mkt_break_lines.mid_dirty) {
|
||||
switch (_settings.net_change) {
|
||||
case LadderSettings.NetChangeTerms.BasisPoints:
|
||||
case LadderSettings.NetChangeTerms.NetBasisPoints:
|
||||
_refresh_prices();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (_mkt_break_lines.mid_dirty)
|
||||
_refresh_mid_mkt_break();
|
||||
}
|
||||
_refresh_fair_value_lines(false);
|
||||
if (_mkt_break_lines.session_low_dirty)
|
||||
_refresh_session_low_line();
|
||||
@@ -1171,16 +1175,8 @@ namespace mbts.spark {
|
||||
_refresh_session_range();
|
||||
if (last_trade_changed) {
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
if (last_index > 0) {
|
||||
LadderRender.render_last(
|
||||
_last_vis_tup.visual,
|
||||
_last_vis_tup.glyph_context,
|
||||
_last_trade,
|
||||
last_index,
|
||||
_settings.row_height,
|
||||
_settings.last_column,
|
||||
_fmt_mkt_depth_item);
|
||||
}
|
||||
if (last_index > 0)
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
|
||||
}
|
||||
if (depth_changed)
|
||||
_refresh_sizes();
|
||||
@@ -1195,27 +1191,25 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_prices(Column column = null) {
|
||||
void _refresh_prices() {
|
||||
if (_settings is null) {
|
||||
return;
|
||||
}
|
||||
column ??= _price_column;
|
||||
var ref_price = _settings.net_change switch {
|
||||
LadderSettings.NetChangeTerms.BasisPoints or
|
||||
LadderSettings.NetChangeTerms.NetBasisPoints or
|
||||
LadderSettings.NetChangeTerms.Ticks => _get_mid_market_break(),
|
||||
_ => 0.0
|
||||
};
|
||||
LadderRender.render_prices(
|
||||
column.visual_tup.visual,
|
||||
column.visual_tup.glyph_context,
|
||||
_price_list,
|
||||
ref_price,
|
||||
_settings.net_change,
|
||||
_settings.row_height,
|
||||
column.settings,
|
||||
_spec,
|
||||
_settings.price_display_format);
|
||||
|
||||
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
|
||||
LadderRender.render_prices(
|
||||
_price_vis_tup.visual, _price_vis_tup.glyph_context,
|
||||
_price_list, _vm.md.settlement_price,
|
||||
_settings.net_change == LadderSettings.NetChangeTerms.Ticks,
|
||||
_settings.row_height, _settings.price_column,
|
||||
_spec);
|
||||
} else {
|
||||
LadderRender.render_prices(
|
||||
_price_vis_tup.visual, _price_vis_tup.glyph_context,
|
||||
_price_list, _settings.row_height,
|
||||
_settings.price_column, _spec,
|
||||
_settings.price_display_format);
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_all_visuals() {
|
||||
@@ -1271,50 +1265,33 @@ 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);
|
||||
}
|
||||
|
||||
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);
|
||||
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, _spec, _settings.bid_column, _fmt_mkt_depth_item, _lighten_market_cells, _bid_market_cell_background);
|
||||
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, _spec, _settings.ask_column, _fmt_mkt_depth_item, _lighten_market_cells, _ask_market_cell_background);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1369,7 +1346,7 @@ namespace mbts.spark {
|
||||
}
|
||||
// regardless of if we've arrived here via a new Last (and new direction arrow)
|
||||
// or we're reusing the old one, this format bit is the same
|
||||
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size, _last_trade.price)} ";
|
||||
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size)} ";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1385,7 +1362,7 @@ namespace mbts.spark {
|
||||
return;
|
||||
if (_price_list is null) {
|
||||
_price_list = new LadderPriceList {
|
||||
regional_tick = _spec
|
||||
security = security
|
||||
};
|
||||
}
|
||||
_price_list.reset(null, null, _initial_row_count);
|
||||
@@ -1422,10 +1399,7 @@ namespace mbts.spark {
|
||||
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);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
return LadderPriceLineViewAdapter.bid_line(best_bid, best_ask, _vm.settlement, _spec);
|
||||
}
|
||||
|
||||
double _normalize_price(double price) =>
|
||||
@@ -1434,20 +1408,13 @@ namespace mbts.spark {
|
||||
double _get_ask_market_break() {
|
||||
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);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
return LadderPriceLineViewAdapter.ask_line(best_bid, best_ask, _vm.settlement, _spec);
|
||||
}
|
||||
|
||||
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);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
return LadderPriceLineViewAdapter.mid_line(best_bid, best_ask, _vm.settlement, _spec);
|
||||
}
|
||||
|
||||
double _get_session_low() => _normalize_price(_get_session_low_raw());
|
||||
@@ -1479,13 +1446,7 @@ namespace mbts.spark {
|
||||
_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;
|
||||
_price_list.security = security;
|
||||
}
|
||||
_bid_vis_tup.glyph_context = glyph_context(s.bid_column);
|
||||
_ask_vis_tup.glyph_context = glyph_context(s.ask_column);
|
||||
@@ -1497,13 +1458,9 @@ namespace mbts.spark {
|
||||
static GlyphContext glyph_context(LadderColumnSettings cs) =>
|
||||
GlyphContext.create(cs.font_family, cs.font_weight);
|
||||
|
||||
var price_display = security.regional_ticks().tick_factors(0).to_api_tick_display_settingsi();
|
||||
_fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(
|
||||
_settings.size_display, _qty_display, price_display);
|
||||
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(
|
||||
_settings.size_display, _qty_display, price_display);
|
||||
_fmt_price_level_impl = MDDisplayHelper.get_formatter_lpl(
|
||||
_settings.size_display, _qty_display, price_display);
|
||||
_settings.size_display, _qty_display);
|
||||
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(_qty_display);
|
||||
_set_columns();
|
||||
_apply_column_visual_offset();
|
||||
if (prev_row_height != _settings.row_height)
|
||||
@@ -1513,16 +1470,18 @@ namespace mbts.spark {
|
||||
_mkt_break_lines.fair_value_line_views.Clear();
|
||||
// Clear and re-add the columns to the viewer to ensure offset/visibility is correct
|
||||
_visual_host.Clear();
|
||||
// The session_range_vis sits beneath the grid backgrounds (on the
|
||||
// z-axis) to avoid obfuscating the font color (dj wants it to be
|
||||
// pure white). Historically, it sat atop the price column, with a
|
||||
// low opacity to slightly darken the range of prices inside of the
|
||||
// session range (i.e., the price-band of the day's trades). In
|
||||
// conjuction with this change, the price column's background
|
||||
// opacity is made slightly less than 1 (in the default settings),
|
||||
// while the font color remains fully opaque, so this layer will
|
||||
// darken the background without affecting the font color.
|
||||
_visual_host.AddVisualChild(_session_range_vis);
|
||||
if (LadderSettings.mimic_tt) {
|
||||
// The session_range_vis sits beneath the grid backgrounds (on the
|
||||
// z-axis) to avoid obfuscating the font color (dj wants it to be
|
||||
// pure white). Historically, it sat atop the price column, with a
|
||||
// low opacity to slightly darken the range of prices inside of the
|
||||
// session range (i.e., the price-band of the day's trades). In
|
||||
// conjuction with this change, the price column's background
|
||||
// opacity is made slightly less than 1 (in the default settings),
|
||||
// while the font color remains fully opaque, so this layer will
|
||||
// darken the background without affecting the font color.
|
||||
_visual_host.AddVisualChild(_session_range_vis);
|
||||
}
|
||||
// Renders the grid lines and column backgrounds
|
||||
_visual_host.AddVisualChild(_gridvis);
|
||||
// Orders column
|
||||
@@ -1562,6 +1521,8 @@ namespace mbts.spark {
|
||||
with_vis => _visual_host.RemoveVisualChild(with_vis.vis));
|
||||
_visual_host.AddVisualChild(_session_low_vis);
|
||||
_visual_host.AddVisualChild(_session_high_vis);
|
||||
if (!LadderSettings.mimic_tt)
|
||||
_visual_host.AddVisualChild(_session_range_vis);
|
||||
_render_grid_background();
|
||||
_init_tactile_adorner();
|
||||
// this effectively sets the background of the "Last" column //deleted to fix color
|
||||
@@ -1570,7 +1531,6 @@ namespace mbts.spark {
|
||||
// }
|
||||
scroll_to_pending_target();
|
||||
visible_columns_changed?.Invoke();
|
||||
_refresh_all_visuals();
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
|
||||
@@ -1578,27 +1538,16 @@ namespace mbts.spark {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_mkt_depth_item_impl;
|
||||
impl ??= MDDisplayHelper.get_formatter_nps(
|
||||
MarketSizeDisplay.get_default(), _qty_display, _spec.tick_factors(0).to_api_tick_display_settingsi());
|
||||
return impl(value);
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
|
||||
string _fmt_price_level(LadderPriceLevel value) {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_price_level_impl;
|
||||
impl ??= MDDisplayHelper.get_formatter_lpl(
|
||||
MarketSizeDisplay.get_default(), _qty_display, security.regional_ticks().tick_factors(0).to_api_tick_display_settingsi());
|
||||
impl ??= MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default(), _qty_display);
|
||||
return impl(value);
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_quantity_impl.
|
||||
string _fmt_mkt_quantity(double value, double price) {
|
||||
string _fmt_mkt_quantity(double value) {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_mkt_quantity_impl;
|
||||
return impl is null ? value.ToString() : impl(value, price);
|
||||
return impl is null ? value.ToString() : impl(value);
|
||||
}
|
||||
|
||||
void _init_tactile_adorner() {
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
namespace mbts.spark;
|
||||
|
||||
using bts;
|
||||
using bts.utils;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
readonly struct LadderPriceLevel : IEquatable<LadderPriceLevel> {
|
||||
public LadderPriceLevel(double price = double.NaN, double volume = 0, double notionalValue = double.NaN) {
|
||||
Price = price;
|
||||
Volume = volume;
|
||||
NotionalValue = notionalValue.is_nan() ? volume * price : notionalValue;
|
||||
}
|
||||
|
||||
public static LadderPriceLevel Empty => new();
|
||||
|
||||
public double Price { get; }
|
||||
public double Volume { get; }
|
||||
public double NotionalValue { get; }
|
||||
|
||||
public LadderPriceLevel Add(double price, double volume) {
|
||||
if (!(volume > 0)) {
|
||||
return this;
|
||||
}
|
||||
if (!(Volume > 0)) {
|
||||
return new(price, volume);
|
||||
}
|
||||
Log.assert(Price.is_real());
|
||||
Log.assert(price.is_real());
|
||||
var newVolume = Volume + volume;
|
||||
var newPrice = (Price * Volume + price * volume) / newVolume;
|
||||
return new(newPrice, newVolume);
|
||||
}
|
||||
|
||||
public LadderPriceLevel Add(IEnumerable<(double Price, double Volume)> items) {
|
||||
var (totalPrice, totalVolume) = items.Sum();
|
||||
return totalVolume > 0
|
||||
? Add(totalPrice / totalVolume, totalVolume)
|
||||
: this;
|
||||
}
|
||||
|
||||
public Builder ToBuilder() =>
|
||||
new() { Cost = Price, Volume = Volume };
|
||||
|
||||
public LadderPriceLevel With(double price = double.NaN, double volume = double.NaN, double notionalValue = double.NaN) =>
|
||||
new(price.if_nan(Price), volume.if_nan(Volume), notionalValue.if_nan(NotionalValue));
|
||||
|
||||
public bool Equals(LadderPriceLevel other) =>
|
||||
MathUtils.epsilon_eq_comparer.Equals(Price, other.Price)
|
||||
&& MathUtils.epsilon_eq_comparer.Equals(Volume, other.Volume);
|
||||
|
||||
public override bool Equals([NotNullWhen(true)] object obj) =>
|
||||
obj is LadderPriceLevel other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(
|
||||
MathUtils.epsilon_eq_comparer.GetHashCode(Price),
|
||||
MathUtils.epsilon_eq_comparer.GetHashCode(Volume));
|
||||
|
||||
public class Builder {
|
||||
double notionalValue = double.NaN;
|
||||
|
||||
public double Cost { get; set; } = double.NaN;
|
||||
public double Volume { get; set; } = 0;
|
||||
public double NotionalValue => notionalValue.is_nan() ? Volume * Cost : notionalValue;
|
||||
|
||||
public void Add(double price, double volume) {
|
||||
if (!(volume > 0)) {
|
||||
return;
|
||||
}
|
||||
Log.assert(price.is_real());
|
||||
Cost += price * volume;
|
||||
Volume += volume;
|
||||
}
|
||||
|
||||
public void Add(double price, double volume, double notionalValue) {
|
||||
if (!(volume > 0)) {
|
||||
return;
|
||||
}
|
||||
Log.assert(price.is_real());
|
||||
Cost += price * volume;
|
||||
Volume += volume;
|
||||
if (this.notionalValue.is_nan()) {
|
||||
this.notionalValue = notionalValue;
|
||||
} else{
|
||||
this.notionalValue += notionalValue;
|
||||
}
|
||||
}
|
||||
|
||||
public LadderPriceLevel ToImmutable() =>
|
||||
Volume > 0
|
||||
? new(Cost / Volume, Volume, NotionalValue)
|
||||
: Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using bts.api;
|
||||
|
||||
namespace mbts.spark {
|
||||
using RegionalTickFactors = user.settings.RegionalTickFactors;
|
||||
|
||||
class LadderPriceLineViewAdapter {
|
||||
public static double bid_line(
|
||||
double bid, double ask, double settlement, RegionalTickFactors spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (!double.IsNaN(bid)) {
|
||||
return bid;
|
||||
}
|
||||
if (!double.IsNaN(ask)) {
|
||||
return _try_tick_down(spec, ask);
|
||||
}
|
||||
return settlement;
|
||||
}
|
||||
}
|
||||
|
||||
public static double ask_line(
|
||||
double bid, double ask, double settlement, RegionalTickFactors spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (!double.IsNaN(ask)) {
|
||||
return _try_tick_down(spec, ask);
|
||||
}
|
||||
if (!double.IsNaN(bid)) {
|
||||
return bid;
|
||||
}
|
||||
return settlement;
|
||||
}
|
||||
}
|
||||
|
||||
public static double mid_line(
|
||||
double bid, double ask, double settlement, RegionalTickFactors spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (double.IsNaN(bid)) {
|
||||
if (double.IsNaN(ask)) {
|
||||
return settlement;
|
||||
}
|
||||
return _try_tick_down(spec, ask);
|
||||
}
|
||||
if (double.IsNaN(ask)) {
|
||||
return bid;
|
||||
}
|
||||
if (spec is not null) {
|
||||
return TickUtils.mid_tick(spec, bid, ask);
|
||||
}
|
||||
return bid;
|
||||
}
|
||||
}
|
||||
|
||||
static double _try_tick_down(RegionalTickFactors spec, double value) {
|
||||
if (spec is not null) {
|
||||
return spec.tickdown(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,34 +16,33 @@ namespace mbts.spark {
|
||||
long _length;
|
||||
long _high_tick;
|
||||
SecurityImmutable? _security;
|
||||
RegionalTickFactors _regional_tick_backing;
|
||||
RegionalTickFactors _regional_tick =>
|
||||
_regional_tick_backing ??= TickUtils.get_regional_ticks(_security);
|
||||
|
||||
public LadderPriceList()
|
||||
: this(RegionalTickFactors.Default) { }
|
||||
|
||||
public LadderPriceList(RegionalTickFactors regional_tick) =>
|
||||
this.regional_tick = regional_tick;
|
||||
public LadderPriceList() { }
|
||||
|
||||
public LadderPriceList(LadderPriceList other) {
|
||||
_length = other._length;
|
||||
_high_tick = other._high_tick;
|
||||
_security = other._security;
|
||||
regional_tick = other.regional_tick;
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
RegionalTickFactors _regional_tick;
|
||||
public RegionalTickFactors regional_tick {
|
||||
get => _regional_tick;
|
||||
public SecurityImmutable? security {
|
||||
get => _security;
|
||||
set {
|
||||
if (value is null) {
|
||||
_regional_tick = RegionalTickFactors.Default;
|
||||
} else {
|
||||
_regional_tick = value;
|
||||
if (value == _security) {
|
||||
return;
|
||||
}
|
||||
_security = value;
|
||||
_regional_tick_backing = null;
|
||||
}
|
||||
}
|
||||
|
||||
public ContractSpecificationImmutable spec { get; set; }
|
||||
|
||||
public long high_tick {
|
||||
get => _high_tick;
|
||||
set => _high_tick = value;
|
||||
@@ -55,8 +54,8 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public double start {
|
||||
get => regional_tick.tickup<double>(0, (uint)high_tick);
|
||||
set => high_tick = regional_tick.tickdiff(value, 0) ?? high_tick;
|
||||
get => _regional_tick.tickup<double>(0, (uint)high_tick);
|
||||
set => high_tick = _regional_tick.tickdiff(value, 0) ?? high_tick;
|
||||
}
|
||||
|
||||
public long length {
|
||||
@@ -81,7 +80,7 @@ namespace mbts.spark {
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public double this[int idx] {
|
||||
get => regional_tick.tick<double>(0, high_tick - idx);
|
||||
get => _regional_tick.tick<double>(0, high_tick - idx);
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -95,7 +94,7 @@ namespace mbts.spark {
|
||||
GetEnumerator();
|
||||
|
||||
public int IndexOf(double item) {
|
||||
var idx = regional_tick.tickdiff(start, item) ?? -1;
|
||||
var idx = _regional_tick.tickdiff(start, item) ?? -1;
|
||||
if (idx < 0 || idx >= length) {
|
||||
return -1;
|
||||
}
|
||||
@@ -210,7 +209,7 @@ namespace mbts.spark {
|
||||
other != null &&
|
||||
_high_tick == other._high_tick &&
|
||||
_length == other._length &&
|
||||
regional_tick.safe_equals(other.regional_tick);
|
||||
_regional_tick.safe_equals(other._regional_tick);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as LadderPriceList);
|
||||
@@ -219,7 +218,7 @@ namespace mbts.spark {
|
||||
HashCode.Combine(_high_tick, _length);
|
||||
|
||||
double _tick_to_price(long tick) =>
|
||||
regional_tick.tick<double>(0, tick);
|
||||
_regional_tick.tick<double>(0, tick);
|
||||
|
||||
void _add_tick(long tick) {
|
||||
int idx;
|
||||
|
||||
@@ -31,19 +31,19 @@ namespace mbts.spark {
|
||||
Side side,
|
||||
double quantity,
|
||||
double filled,
|
||||
double price,
|
||||
LadderSettings.BadgeStyle style,
|
||||
Func<double, double, string> fmt_value = null
|
||||
Func<double, string> fmt_value = null
|
||||
) {
|
||||
fmt_value ??= (v, p) => v.ToString();
|
||||
if (fmt_value is null)
|
||||
fmt_value = FormattingDelegates.default_string<double>();
|
||||
switch (style) {
|
||||
case LadderSettings.BadgeStyle.Alternate:
|
||||
var working_qty = quantity > filled ? quantity - filled : 0;
|
||||
return new[] {
|
||||
$"{side.abbreviate()}:{fmt_value(filled, price)}",
|
||||
$"W:{fmt_value(working_qty, price)}"
|
||||
$"{side.abbreviate()}:{fmt_value(filled)}",
|
||||
$"W:{fmt_value(working_qty)}"
|
||||
};
|
||||
default: return new[] { $"{fmt_value(filled, price)}/{fmt_value(quantity, price)}" };
|
||||
default: return new[] { $"{fmt_value(filled)}/{fmt_value(quantity)}" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,8 @@ namespace mbts.spark {
|
||||
static class LadderRender {
|
||||
// Note: the orders-column has a separate "row_padding" value: Badge.DefaultMargin
|
||||
// (see how it is incorporated in measure_row_height() below)
|
||||
public static Thickness row_padding => new Thickness(4, 2, 4, 2);
|
||||
public static Thickness row_padding => LadderSettings.mimic_tt ?
|
||||
new Thickness(4, 2, 4, 2) : new Thickness(6);
|
||||
|
||||
const string tick_separator_period = ".";
|
||||
const string tick_separator_dash = "-";
|
||||
@@ -101,64 +102,17 @@ namespace mbts.spark {
|
||||
settings.font_size, settings.width, row_height, HorizontalAlignment.Right,
|
||||
-4, VerticalAlignment.Center, row * row_height, true);
|
||||
|
||||
public static GlyphRun[] format_prices(
|
||||
IList<double> prices,
|
||||
double reference_price,
|
||||
LadderSettings.NetChangeTerms terms,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
GlyphContext glyph_context,
|
||||
RegionalTickFactors spec,
|
||||
LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default
|
||||
) {
|
||||
public static GlyphRun[] format_prices(IList<double> prices, double reference_price, bool format_in_tick_terms, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, RegionalTickFactors spec) {
|
||||
var result = new GlyphRun[prices.Count];
|
||||
string basis_pts_fmt = terms switch {
|
||||
LadderSettings.NetChangeTerms.BasisPoints or
|
||||
LadderSettings.NetChangeTerms.NetBasisPoints => get_basis_pts_fmt(),
|
||||
_ => null
|
||||
};
|
||||
Parallel.For(0, prices.Count, i =>
|
||||
result[i] = format_text(fmt(i), i, row_height, settings, glyph_context, price_display_format));
|
||||
result[i] = format_text(spec.format(prices[i] - reference_price, format_in_tick_terms), i, row_height, settings, glyph_context));
|
||||
return result;
|
||||
}
|
||||
public static GlyphRun[] format_prices(IList<double> prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, RegionalTickFactors spec, LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
var result = new GlyphRun[prices.Count];
|
||||
Parallel.For(0, prices.Count, i =>
|
||||
result[i] = format_text(spec.format(prices[i]), i, row_height, settings, glyph_context, price_display_format));
|
||||
return result;
|
||||
|
||||
string fmt(int i) {
|
||||
switch (terms) {
|
||||
case LadderSettings.NetChangeTerms.Dollars:
|
||||
return prices[i].ToString("0.00");
|
||||
case LadderSettings.NetChangeTerms.BasisPoints: {
|
||||
if (!reference_price.is_real_non_zero()) {
|
||||
return spec.format(double.NaN);
|
||||
}
|
||||
var basis_pts = prices[i] * 10000.0 / reference_price;
|
||||
return basis_pts.ToString(basis_pts_fmt);
|
||||
}
|
||||
case LadderSettings.NetChangeTerms.NetBasisPoints: {
|
||||
if (!reference_price.is_real_non_zero()) {
|
||||
return spec.format(double.NaN);
|
||||
}
|
||||
var net_basis_pts = prices[i] * 10000.0 / reference_price - 10000.0;
|
||||
return net_basis_pts.ToString(basis_pts_fmt);
|
||||
}
|
||||
default:
|
||||
return spec.format(prices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
string get_basis_pts_fmt() {
|
||||
if (!reference_price.is_real_non_zero()) {
|
||||
return "0.000";
|
||||
}
|
||||
var tick_sz = spec.ticksize(reference_price);
|
||||
var basis_pt = reference_price.abs() / 10000.0;
|
||||
var precision_log10 = Math.Log10(tick_sz / basis_pt);
|
||||
int n = 0;
|
||||
if (precision_log10 < 0) {
|
||||
n = (int)Math.Ceiling(-precision_log10);
|
||||
}
|
||||
return n < 1
|
||||
? "0"
|
||||
: "0." + new string('0', n);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear_prices(DrawingVisual price, int row_count, double row_height, LadderColumnSettings settings, GlyphContext glyph_context) {
|
||||
@@ -174,20 +128,51 @@ namespace mbts.spark {
|
||||
public static void render_prices(
|
||||
DrawingVisual price,
|
||||
GlyphContext glyph_context,
|
||||
IList<double> dprices,
|
||||
IList<double> price_list,
|
||||
double wrt_price,
|
||||
LadderSettings.NetChangeTerms terms,
|
||||
bool format_in_tick_terms,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
RegionalTickFactors tick) {
|
||||
if (price_list.IsNullOrEmpty()) {
|
||||
return;
|
||||
}
|
||||
render_prices(
|
||||
price, glyph_context, price_list, row_height, settings, tick,
|
||||
wrt_price, format_in_tick_terms);
|
||||
}
|
||||
|
||||
public static void render_prices(
|
||||
DrawingVisual price,
|
||||
GlyphContext glyph_context,
|
||||
IList<double> dprices,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
RegionalTickFactors tick,
|
||||
LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default) {
|
||||
if (dprices.IsNullOrEmpty()) {
|
||||
return;
|
||||
}
|
||||
double wrt_price,
|
||||
bool format_in_tick_terms) {
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
var fprices = format_prices(
|
||||
dprices, wrt_price, terms, row_height, settings, glyph_context, tick, price_display_format);
|
||||
var fprices = format_prices(dprices, wrt_price, format_in_tick_terms, row_height, settings, glyph_context, tick);
|
||||
using (var dc = price.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, dprices.Count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
|
||||
for (int x = 0; x < dprices.Count; ++x)
|
||||
dc.DrawGlyphRun(foreground, fprices[x]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void render_prices(
|
||||
DrawingVisual price,
|
||||
GlyphContext glyph_context,
|
||||
IList<double> dprices,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
RegionalTickFactors tick,
|
||||
LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
var fprices = format_prices(dprices, row_height, settings, glyph_context, tick, price_display_format);
|
||||
using (var dc = price.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, dprices.Count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
|
||||
@@ -213,78 +198,69 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static void RenderSizes(
|
||||
DrawingVisual dv,
|
||||
GlyphContext glyphContext,
|
||||
int originIndex,
|
||||
IList<double> prices,
|
||||
IEnumerable<LadderPriceLevel> levels,
|
||||
double rowHeight,
|
||||
RegionalTickFactors spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<LadderPriceLevel, string> fmt) {
|
||||
var bg = settings.background.rgb_color().blend((BTSColor)0x2AFFFFFF).to_brush();
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, rowHeight, prices.Count))
|
||||
using (var levelsEn = (levels ?? Enumerable.Empty<LadderPriceLevel>()).GetEnumerator()) {
|
||||
var fg = settings.foreground.rgb_brush();
|
||||
var currIdx = originIndex;
|
||||
var currPrice = prices[currIdx];
|
||||
while (levelsEn.MoveNext()) {
|
||||
var level = levelsEn.Current;
|
||||
var ticks = spec.count_ticks(level.Price);
|
||||
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : level.Price;
|
||||
// prices are in descending order, so get diff from curr_price - price
|
||||
var tickDiff = spec.tickdiff(currPrice, price).assert_value();
|
||||
if (!MathUtils.try_increment(ref currIdx, tickDiff) || currIdx < 0 || currIdx >= prices.Count) {
|
||||
continue;
|
||||
}
|
||||
currPrice = price;
|
||||
if (bg is not null) {
|
||||
var y = currIdx * rowHeight + GRID_LINE_HEIGHT;
|
||||
var height = rowHeight - GRID_LINE_HEIGHT;
|
||||
dc.DrawRectangle(bg, null, new Rect(0, y, settings.width, height));
|
||||
}
|
||||
var gr = format_text(fmt(level), currIdx, rowHeight, settings, glyphContext);
|
||||
dc.DrawGlyphRun(fg, gr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<LadderPriceLevel> GetLevels(IEnumerable<(double Price, double Volume, double NotionalValue)> depth, RegionalTickFactors spec) =>
|
||||
depth.TakeWhile(nps => nps.Volume > 0 && nps.Price.is_real())
|
||||
.AggregateConsecutiveGroups(
|
||||
LadderPriceLevel.Empty.ToBuilder,
|
||||
(bd, rung) => {
|
||||
bd.Add(rung.Price, rung.Volume, rung.NotionalValue);
|
||||
return bd;
|
||||
},
|
||||
rung => {
|
||||
var ticks = spec.count_ticks(rung.Price);
|
||||
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.Price;
|
||||
})
|
||||
.Select(bd => bd.ToImmutable());
|
||||
|
||||
public static void render_sizes(
|
||||
DrawingVisual dv,
|
||||
GlyphContext glyph_context,
|
||||
int origin_index,
|
||||
IList<double> prices,
|
||||
IEnumerable<(double Price, double Volume, double NotionalValue)> rungs,
|
||||
NativePriceSize[] rungs,
|
||||
double row_height,
|
||||
RegionalTickFactors spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<LadderPriceLevel, string> fmt) {
|
||||
RenderSizes(
|
||||
dv,
|
||||
glyph_context,
|
||||
origin_index,
|
||||
prices,
|
||||
GetLevels(rungs, spec),
|
||||
row_height,
|
||||
spec,
|
||||
settings,
|
||||
fmt);
|
||||
Func<NativePriceSize, string> fmt,
|
||||
bool lighten_market_cells,
|
||||
Color? market_cell_background) {
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
Brush market_cell_background_brush;
|
||||
if (market_cell_background.HasValue) {
|
||||
market_cell_background_brush = new SolidColorBrush(market_cell_background.Value);
|
||||
} else if (lighten_market_cells) {
|
||||
market_cell_background_brush = settings.background.rgb_color()
|
||||
.blend((BTSColor)0x2AFFFFFF)
|
||||
.to_brush();
|
||||
} else {
|
||||
market_cell_background_brush = null;
|
||||
}
|
||||
// market_cell_background_brush = settings.background.rgb_color().blend((BTSColor)0xFF000000).to_brush();
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, prices.Count * row_height));
|
||||
if (!prices.within_range(origin_index) || rungs.is_null_or_empty())
|
||||
return;
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
int curr_index = origin_index;
|
||||
double curr_price = prices[curr_index];
|
||||
NativePriceSize curr_size = NativePriceSize.NO_MARKET;
|
||||
var row_tick_groups = rungs
|
||||
.TakeWhile(nps => !nps.is_nan)
|
||||
.AggregateConsecutiveGroups(() => NativePriceSize.NO_MARKET,
|
||||
(aggr, rung) => rung.with(
|
||||
direct_size: aggr.direct_size + rung.direct_size,
|
||||
implied_size: aggr.implied_size + rung.implied_size),
|
||||
rung => {
|
||||
var ticks = spec.count_ticks(rung.price);
|
||||
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
|
||||
});
|
||||
foreach (var rung in row_tick_groups) {
|
||||
//double price = spec.round_to_tick(rung.price);
|
||||
var ticks = spec.count_ticks(rung.price);
|
||||
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
|
||||
// prices are in descending order, so get diff from curr_price - price
|
||||
var tick_diff = spec.tickdiff(curr_price, price).assert_value();
|
||||
if (!MathUtils.try_increment(ref curr_index, tick_diff)) {
|
||||
continue;
|
||||
}
|
||||
curr_price = price;
|
||||
if (curr_index >= 0 && curr_index < prices.Count) {
|
||||
if (market_cell_background_brush != null)
|
||||
dc.DrawRectangle(market_cell_background_brush, null,
|
||||
new Rect(0, curr_index * row_height + GRID_LINE_HEIGHT,
|
||||
settings.width, row_height - GRID_LINE_HEIGHT));
|
||||
var gr = format_text(fmt(rung), curr_index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(foreground, gr);
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void render_volumes(
|
||||
@@ -297,7 +273,7 @@ namespace mbts.spark {
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
string last_size_and_direction,
|
||||
Func<double, double, string> fmt_value) {
|
||||
Func<double, string> fmt_value) {
|
||||
//
|
||||
if (!data.Any()) {
|
||||
_clear_visual(dv);
|
||||
@@ -320,12 +296,12 @@ namespace mbts.spark {
|
||||
_clear_visual(dv);
|
||||
return;
|
||||
}
|
||||
fmt_value ??= (v, p) => v.ToString();
|
||||
fmt_value ??= FormattingDelegates.default_string<double>();
|
||||
// inflate the max so that the graph never fills the available space
|
||||
double max = data.Max(v => v.Volume) * 1.1;
|
||||
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
|
||||
/// local methods
|
||||
void render_cell(DrawingContext dc, int r, double price, VolumeAtPriceLevelImmutable vap) {
|
||||
void render_cell(DrawingContext dc, int r, VolumeAtPriceLevelImmutable vap) {
|
||||
Brush text_brush = settings.general.foreground.rgb_brush();
|
||||
double y = row_height * r;
|
||||
var last_trade_here = r == last_index;
|
||||
@@ -352,11 +328,11 @@ namespace mbts.spark {
|
||||
}
|
||||
x_bar += width;
|
||||
if (show_numbers)
|
||||
numbers_text.Add(fmt_value(v, vap.Price));
|
||||
numbers_text.Add(fmt_value(v));
|
||||
}
|
||||
|
||||
if (settings.total.show_numbers) {
|
||||
numbers_text.Add(fmt_value(vap.Volume, vap.Price));
|
||||
numbers_text.Add(fmt_value(vap.Volume));
|
||||
}
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
numbers_text.Clear();
|
||||
@@ -383,17 +359,9 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void RenderCell<T>(DrawingContext dc, int row, double price, T data_item);
|
||||
public delegate void RenderCell<T>(DrawingContext dc, int row, T data_item);
|
||||
|
||||
static void _render_cells<T>(
|
||||
DrawingVisual dv,
|
||||
double row_height,
|
||||
IEnumerable<(double price, T value)> cells,
|
||||
LadderColumnSettings settings,
|
||||
IList<double> prices,
|
||||
RegionalTickFactors spec,
|
||||
RenderCell<T> render_cell
|
||||
) {
|
||||
static void _render_cells<T>(DrawingVisual dv, double row_height, IEnumerable<(double price, T value)> cells, LadderColumnSettings settings, IList<double> prices, RegionalTickFactors spec, RenderCell<T> render_cell) {
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
|
||||
if (prices.is_null_or_empty() || cells.is_null_or_empty()) {
|
||||
@@ -413,7 +381,7 @@ namespace mbts.spark {
|
||||
var check_price = spec.tickdown(max_price, idx);
|
||||
Log.assert(spec.tick_equals(price, check_price));
|
||||
Log.assert(idx >= 0 && idx < prices.Count);
|
||||
render_cell(dc, idx, price, value);
|
||||
render_cell(dc, idx, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -425,10 +393,10 @@ namespace mbts.spark {
|
||||
int index,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
Func<NativePriceSize, string> fmt_value) {
|
||||
fmt_value ??= nps => nps.size.ToString();
|
||||
Func<double, string> fmt_value) {
|
||||
fmt_value ??= FormattingDelegates.default_string<double>();
|
||||
using (var dc = dv.RenderOpen()) {
|
||||
var gr = format_text(fmt_value(last), index, row_height, settings, glyph_context);
|
||||
var gr = format_text(fmt_value(last.size), index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(settings.foreground.rgb_brush(), gr);
|
||||
}
|
||||
}
|
||||
@@ -442,13 +410,13 @@ namespace mbts.spark {
|
||||
double row_height,
|
||||
RegionalTickFactors spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<double, double, string> fmt_value
|
||||
Func<double, string> fmt_value
|
||||
) {
|
||||
fmt_value ??= (v, p) => v.ToString();
|
||||
fmt_value ??= FormattingDelegates.default_string<double>();
|
||||
var fg = settings.foreground.rgb_brush();
|
||||
_render_cells(dv, row_height, pvp, settings, prices, spec, (dc, row, price, piq) =>
|
||||
_render_cells(dv, row_height, pvp, settings, prices, spec, (dc, row, piq) =>
|
||||
dc.DrawGlyphRun(
|
||||
fg, format_text(fmt_value(piq, price), row, row_height, settings, glyph_context)));
|
||||
fg, format_text(fmt_value(piq), row, row_height, settings, glyph_context)));
|
||||
}
|
||||
|
||||
public static void render_mkt_break(DrawingVisual dv, int index, double row_height, uint color) =>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -39,9 +39,6 @@
|
||||
|
||||
<cv:EnumCharacterCasingConverter x:Key="casing_converter"/>
|
||||
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel" />
|
||||
<cv:ResponsiveTextConverter x:Key="BidPriceTextConverter" LongText="Bid:" ShortText="B:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="AskPriceTextConverter" LongText="Ask:" ShortText="A:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="MidPriceTextConverter" LongText="Mid:" ShortText="M:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="DayTextConverter" LongText="Day:" ShortText="D:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="HighTextConverter" LongText="High:" ShortText="H:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="LowTextConverter" LongText="Low:" ShortText="L:" Threshold="99" />
|
||||
@@ -182,7 +179,7 @@
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CancelAll" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Grid.Row" Value="2" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center">Cancel All</TextBlock>
|
||||
@@ -220,7 +217,10 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Style x:Key="TT_CancelAll" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
</Style>
|
||||
<Style x:Key="TT_CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Setter Property="Grid.Row" Value="1" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
@@ -228,7 +228,7 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Style x:Key="TT_CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Setter Property="Grid.Row" Value="2" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
@@ -236,6 +236,83 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Grid.Row" Value="0" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{Binding bid_foreground}">
|
||||
Cancel Buys
|
||||
</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{u:BTSColor UI.Button.normal_border}"
|
||||
Background="{Binding bid_background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
Name="button_content" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#99167ed3" />
|
||||
<Setter
|
||||
TargetName="Border"
|
||||
Property="BorderBrush"
|
||||
Value="{u:BTSColor UI.Button.mouseover_border}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}" />
|
||||
<Setter Property="Grid.Row" Value="1" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{Binding ask_foreground}">
|
||||
Cancel Sells
|
||||
</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{u:BTSColor UI.Button.normal_border}"
|
||||
Background="{Binding ask_background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True" >
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
Name="button_content" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
|
||||
<Setter
|
||||
TargetName="Border"
|
||||
Property="BorderBrush"
|
||||
Value="{u:BTSColor UI.Button.mouseover_border}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="SettingsButton" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
@@ -286,103 +363,13 @@
|
||||
<Setter Property="Grid.Column" Value="2"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Margin" Value="0,0,0,2"/>
|
||||
<Setter Property="Padding" Value="4,1"/>
|
||||
<Setter Property="Padding" Value="1"/>
|
||||
</Style>
|
||||
<Style x:Key="LeftPanelPricePointText" TargetType="TextBlock">
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LeftPanelPricePointButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Grid.Column" Value="2" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||
<Setter Property="Margin" Value="0,0,0,2" />
|
||||
<Setter Property="Padding" Value="4,1" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="0"
|
||||
Height="Auto"
|
||||
Padding="0"
|
||||
SnapsToDevicePixels="True">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="CommonStates">
|
||||
<VisualState Name="Normal" />
|
||||
|
||||
<VisualState Name="MouseOver">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="HoverRect" Storyboard.TargetProperty="Fill.Opacity" BeginTime="0" Duration="00:00:00.15" To="0.20">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<QuadraticEase />
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.mouseover_border}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.pressed_background}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.pressed_border}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
<VisualState Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.disabled_background}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.disabled_border}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<VisualStateGroup Name="FocusStates">
|
||||
<VisualState Name="Unfocused" />
|
||||
|
||||
<VisualState Name="Focused">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.focused_border}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Rectangle Name="HoverRect" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" IsHitTestVisible="False">
|
||||
<Rectangle.Fill>
|
||||
<SolidColorBrush Color="White" Opacity="0" />
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
<ContentPresenter
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
RecognizesAccessKey="True" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<sys:Double x:Key="MaxZoomFactor">1.5</sys:Double>
|
||||
<sys:Double x:Key="MinZoomFactor">0.5</sys:Double>
|
||||
</ResourceDictionary>
|
||||
@@ -447,9 +434,6 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -457,123 +441,9 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Ask Price (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource AskPriceTextConverter}}"
|
||||
Visibility="{Binding
|
||||
ask_price_style.actual_show_label,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Ask Price (value) -->
|
||||
<Button
|
||||
Grid.Row="0"
|
||||
Visibility="{Binding
|
||||
ask_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Ask"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding ask_price}"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding ask_price_style.background_color}" Opacity="1" />
|
||||
</Button.Background>
|
||||
<TextBlock
|
||||
FontFamily="{Binding ask_price_style.font_family}"
|
||||
FontSize="{Binding ask_price_style.font_size}"
|
||||
FontWeight="{Binding ask_price_style.font_weight}"
|
||||
Foreground="{Binding
|
||||
ask_price_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="ask_price"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<!-- Mid Price (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource MidPriceTextConverter}}"
|
||||
Visibility="{Binding
|
||||
mid_price_style.actual_show_label,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Mid Price (value) -->
|
||||
<Button
|
||||
Grid.Row="1"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding mid_price}"
|
||||
Visibility="{Binding
|
||||
mid_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Mid"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding mid_price_style.background_color}" Opacity="1" />
|
||||
</Button.Background>
|
||||
<TextBlock
|
||||
FontFamily="{Binding mid_price_style.font_family}"
|
||||
FontSize="{Binding mid_price_style.font_size}"
|
||||
FontWeight="{Binding mid_price_style.font_weight}"
|
||||
Foreground="{Binding
|
||||
mid_price_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="mid_price"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<!-- Bid Price (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource BidPriceTextConverter}}"
|
||||
Visibility="{Binding
|
||||
bid_price_style.actual_show_label,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Bid Price (value) -->
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding bid_price}"
|
||||
Visibility="{Binding
|
||||
bid_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Bid"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding bid_price_style.background_color}" Opacity="1" />
|
||||
</Button.Background>
|
||||
<TextBlock
|
||||
FontFamily="{Binding bid_price_style.font_family}"
|
||||
FontSize="{Binding bid_price_style.font_size}"
|
||||
FontWeight="{Binding bid_price_style.font_weight}"
|
||||
Foreground="{Binding
|
||||
bid_price_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="bid_price"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<!-- Day Change (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Row="0"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource DayTextConverter}}"
|
||||
Visibility="{Binding
|
||||
day_change_style.actual_show_label,
|
||||
@@ -582,7 +452,7 @@
|
||||
|
||||
<!-- Day Change (value) -->
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
Grid.Row="0"
|
||||
Background="{Binding
|
||||
day_change_background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
@@ -609,7 +479,7 @@
|
||||
|
||||
<!-- High (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Row="1"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource HighTextConverter}}"
|
||||
Visibility="{Binding
|
||||
session_high_style.actual_show_label,
|
||||
@@ -617,17 +487,15 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- High (value) -->
|
||||
<Button
|
||||
Grid.Row="4"
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Background="{Binding
|
||||
session_high_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding session_high}"
|
||||
Visibility="{Binding
|
||||
session_high_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Session High"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding session_high_style.font_family}"
|
||||
FontSize="{Binding session_high_style.font_size}"
|
||||
@@ -636,19 +504,24 @@
|
||||
session_high_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_high"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
<Hyperlink
|
||||
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding session_high}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_high"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
</Border>
|
||||
|
||||
<!-- Low (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Row="2"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource LowTextConverter}}"
|
||||
Visibility="{Binding
|
||||
session_low_style.actual_show_label,
|
||||
@@ -656,17 +529,15 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Low (value) -->
|
||||
<Button
|
||||
Grid.Row="5"
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Background="{Binding
|
||||
session_low_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding session_low}"
|
||||
Visibility="{Binding
|
||||
session_low_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Session Low"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding session_low_style.font_family}"
|
||||
FontSize="{Binding session_low_style.font_size}"
|
||||
@@ -675,19 +546,24 @@
|
||||
session_low_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_low"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
<Hyperlink
|
||||
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding session_low}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_low"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
</Border>
|
||||
|
||||
<!-- Open (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="6"
|
||||
Grid.Row="3"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource OpenTextConverter}}"
|
||||
Visibility="{Binding
|
||||
open_style.actual_show_label,
|
||||
@@ -695,17 +571,15 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Open (value) -->
|
||||
<Button
|
||||
Grid.Row="6"
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
Background="{Binding
|
||||
open_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding open}"
|
||||
Visibility="{Binding
|
||||
open_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Open"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding open_style.font_family}"
|
||||
FontSize="{Binding open_style.font_size}"
|
||||
@@ -713,19 +587,23 @@
|
||||
Foreground="{Binding
|
||||
open_style.font_color, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="open"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
<Hyperlink Command="{Binding go_to_price_cmd}" CommandParameter="{Binding open}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="open"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
</Border>
|
||||
|
||||
<!-- Settlement (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="7"
|
||||
Grid.Row="4"
|
||||
Text="{Binding
|
||||
left_panel_width, Converter={StaticResource SettlementTextConverter}}"
|
||||
Visibility="{Binding
|
||||
@@ -734,17 +612,15 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Settlement (value) -->
|
||||
<Button
|
||||
Grid.Row="7"
|
||||
<Border
|
||||
Grid.Row="4"
|
||||
Background="{Binding
|
||||
settlement_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding settlement}"
|
||||
Visibility="{Binding
|
||||
settlement_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Settlement"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding settlement_style.font_family}"
|
||||
FontSize="{Binding settlement_style.font_size}"
|
||||
@@ -753,18 +629,23 @@
|
||||
settlement_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="settlement"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
<Hyperlink
|
||||
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding settlement}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="settlement"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
</Border>
|
||||
|
||||
<!-- Theo (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="8"
|
||||
Grid.Row="5"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource TheoTextConverter}}"
|
||||
Visibility="{Binding
|
||||
theo_style.actual_show_label,
|
||||
@@ -772,17 +653,15 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Theo (value) -->
|
||||
<Button
|
||||
Grid.Row="8"
|
||||
<Border
|
||||
Grid.Row="5"
|
||||
Background="{Binding
|
||||
theo_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding theo}"
|
||||
Visibility="{Binding
|
||||
theo_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Theo"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding theo_style.font_family}"
|
||||
FontSize="{Binding theo_style.font_size}"
|
||||
@@ -790,19 +669,23 @@
|
||||
Foreground="{Binding
|
||||
theo_style.font_color, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="theo"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
<Hyperlink Command="{Binding go_to_price_cmd}" CommandParameter="{Binding theo}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="theo"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
</Border>
|
||||
|
||||
<!-- Delta -->
|
||||
<TextBlock
|
||||
Grid.Row="9"
|
||||
Grid.Row="6"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource DeltaTextConverter}}"
|
||||
Visibility="{Binding
|
||||
delta_style.actual_show_label,
|
||||
@@ -810,7 +693,7 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<Border
|
||||
Grid.Row="9"
|
||||
Grid.Row="6"
|
||||
Background="{Binding
|
||||
delta_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
@@ -831,7 +714,7 @@
|
||||
|
||||
<!-- Vega -->
|
||||
<TextBlock
|
||||
Grid.Row="10"
|
||||
Grid.Row="7"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource VegaTextConverter}}"
|
||||
Visibility="{Binding
|
||||
vega_style.actual_show_label,
|
||||
@@ -839,7 +722,7 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<Border
|
||||
Grid.Row="10"
|
||||
Grid.Row="7"
|
||||
Background="{Binding
|
||||
vega_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
@@ -860,7 +743,7 @@
|
||||
<!-- Volume (label) -->
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Grid.Row="11"
|
||||
Grid.Row="8"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource VolumeTextConverter}}"
|
||||
Visibility="{Binding
|
||||
volume_style.actual_show_label,
|
||||
@@ -869,7 +752,7 @@
|
||||
|
||||
<!-- Volume (price) -->
|
||||
<Border
|
||||
Grid.Row="11"
|
||||
Grid.Row="8"
|
||||
Background="{Binding
|
||||
volume_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
@@ -910,7 +793,6 @@
|
||||
Margin="2,10,2,2"
|
||||
ItemsSource="{Binding accounts}"
|
||||
SelectedItem="{Binding selected_account}"
|
||||
ToolTip="Account"
|
||||
Visibility="{Binding
|
||||
DataContext.show_account,
|
||||
RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
@@ -920,7 +802,6 @@
|
||||
<ComboBox
|
||||
DataContext="{Binding order_properties_vm}" ItemsSource="{Binding time_in_forces}"
|
||||
SelectedItem="{Binding selected_time_in_force}" Margin="2"
|
||||
ToolTip="Time In Force"
|
||||
Visibility="{Binding
|
||||
DataContext.show_tif,
|
||||
RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
@@ -943,7 +824,6 @@
|
||||
IsOpen="{Binding is_expanded}"
|
||||
ItemsSource="{Binding algo_definitions_view}"
|
||||
SelectedItem="{Binding selected_algo_definition}"
|
||||
ToolTip="Order/Algo Type"
|
||||
Visibility="{Binding
|
||||
DataContext.show_order_type,
|
||||
RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
@@ -999,7 +879,6 @@
|
||||
Increment="{Binding size_calculator.unit_quantity}"
|
||||
Minimum="0"
|
||||
TickDisplaySettings="{Binding size_calculator.display_settings}"
|
||||
ToolTip="Size"
|
||||
Value="{Binding size_calculator.display_value, Mode=TwoWay}" />
|
||||
|
||||
<!-- Qty quick buttons -->
|
||||
@@ -1022,8 +901,7 @@
|
||||
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
Content="{Binding}"
|
||||
Focusable="False"
|
||||
Foreground="{u:BTSColor UI.Button.normal_foreground}"
|
||||
ToolTip="Add to Size">
|
||||
Foreground="{u:BTSColor UI.Button.normal_foreground}">
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding
|
||||
Converter="{x:Static cv:DynamicDoubleMultiplierConverter.Instance}">
|
||||
@@ -1043,7 +921,6 @@
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding size_calculator.clear}"
|
||||
ToolTip="Clear Size"
|
||||
Visibility="{Binding
|
||||
show_clear, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource CommonButton}">
|
||||
@@ -1054,7 +931,6 @@
|
||||
<CheckBox
|
||||
Margin="2"
|
||||
IsChecked="{Binding auto_clear}"
|
||||
ToolTip="Clear Size after order"
|
||||
Visibility="{Binding
|
||||
show_auto_clear, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
Auto Clear
|
||||
@@ -1097,19 +973,19 @@
|
||||
<Button
|
||||
Command="{Binding cancel_cmd}"
|
||||
CommandParameter="Bids"
|
||||
Style="{StaticResource CancelBuys}"
|
||||
Style="{StaticResource {x:Static sp:LadderView.CancelBuysButtonStyleKey}}"
|
||||
Visibility="{Binding
|
||||
show_cancel_buys, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<Button
|
||||
Command="{Binding cancel_cmd}"
|
||||
CommandParameter="Asks"
|
||||
Style="{StaticResource CancelSells}"
|
||||
Style="{StaticResource {x:Static sp:LadderView.CancelSellsButtonStyleKey}}"
|
||||
Visibility="{Binding
|
||||
show_cancel_sells, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<Button
|
||||
Command="{Binding cancel_cmd}"
|
||||
CommandParameter="All"
|
||||
Style="{StaticResource CancelAll}"
|
||||
Style="{StaticResource {x:Static sp:LadderView.CancelAllButtonStyleKey}}"
|
||||
Visibility="{Binding
|
||||
show_cancel_all, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
|
||||
@@ -27,6 +27,22 @@ namespace mbts.spark {
|
||||
MinZoomFactor = 0.5,
|
||||
MaxZoomFactor = 1.5;
|
||||
|
||||
static LadderView() {
|
||||
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
|
||||
MimicTT = knobs.MimicTt;
|
||||
}
|
||||
|
||||
public static bool MimicTT { get; }
|
||||
|
||||
public static string CancelBuysButtonStyleKey =>
|
||||
MimicTT ? "TT_CancelBuys" : "CancelBuys";
|
||||
|
||||
public static string CancelSellsButtonStyleKey =>
|
||||
MimicTT ? "TT_CancelSells" : "CancelSells";
|
||||
|
||||
public static string CancelAllButtonStyleKey =>
|
||||
MimicTT ? "TT_CancelAll" : "CancelAll";
|
||||
|
||||
public static IMultiValueConverter PositionToolTipConverter =
|
||||
ValueConverter.create<AccountTuple?, bool, bool, bool, string>((account, show_day_pos, show_raw_day_pos, show_total_pos) => {
|
||||
const string day_pos_text = "day", raw_day_pos_text = "raw day", total_pos_text = "total";
|
||||
@@ -60,8 +76,14 @@ namespace mbts.spark {
|
||||
if (show_total_pos) add_part(total_pos_text, " (", ")");
|
||||
//
|
||||
if (part_count > 1) {
|
||||
// This is a complex expression (showing more than 1 value) - wrap it in single quotes.
|
||||
// Explicitly state which value will be copied to the size input.
|
||||
sb.Insert(0, "'");
|
||||
sb.Add("'");
|
||||
sb.Add(" position\n(click to copy ", primary_pos, " to size)");
|
||||
} else {
|
||||
// Only showing 1 value, no need for single quotes,
|
||||
// nor an explicit statement that indicates which value will be copied to the size input.
|
||||
sb.Add(" position\n(click to copy to size)");
|
||||
}
|
||||
return sb.ToString();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using bts;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
@@ -223,10 +223,6 @@ namespace mbts.spark {
|
||||
Add(qty_column = new SizeColumn(
|
||||
new DoubleMinMax<OrderRecordTupleWithRoot>(
|
||||
ort => ort.order.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
// notional value
|
||||
Add(notional_value_column = new NotionalValueColumn(
|
||||
new DoubleMinMax<OrderRecordTupleWithRoot>(
|
||||
ort => ort.notional_value, nameof(OrderBookRowVM.notional_value))));
|
||||
// edit quantity column
|
||||
// not usually part of the main order book, but it's useful to add it
|
||||
// rather than debugging the popup window
|
||||
@@ -236,10 +232,6 @@ namespace mbts.spark {
|
||||
new DoubleMinMax<OrderRecordTupleWithRoot>(
|
||||
ort => ort.order.order.filled_quantity(),
|
||||
nameof(OrderBookRowVM.filled_quantity))));
|
||||
// filled notional value
|
||||
Add(filled_notional_value_column = new FilledNotionalValueColumn(
|
||||
new DoubleMinMax<OrderRecordTupleWithRoot>(
|
||||
ort => ort.filled_notional_value, nameof(OrderBookRowVM.filled_notional_value))));
|
||||
// price
|
||||
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.Price,
|
||||
@@ -371,11 +363,9 @@ namespace mbts.spark {
|
||||
public SimpleTextColumn<OrderSubType[]> sub_type_column { get; }
|
||||
public SideColumn side_column { get; }
|
||||
public SizeColumn qty_column { get; }
|
||||
public NotionalValueColumn notional_value_column { get; }
|
||||
public EditableQtyColumn edit_qty_column { get; }
|
||||
public SimpleTextColumn<DoubleMinMaxState> price_column { get; }
|
||||
public FilledColumn filled_qty_column { get; }
|
||||
public FilledNotionalValueColumn filled_notional_value_column { get; }
|
||||
public SimpleTextColumn<DoubleMinMaxState> last_price_column { get; }
|
||||
public SimpleTextColumn<DoubleMinMaxState> avg_price_column { get; }
|
||||
public SimpleTextColumn<OrderStatus[]> status_column { get; }
|
||||
@@ -401,10 +391,8 @@ namespace mbts.spark {
|
||||
order_sub_types = sub_type_column.filter.get_state(),
|
||||
sides = side_column.filter.get_state(),
|
||||
qty = qty_column.filter.get_state(),
|
||||
notional_value = notional_value_column.filter.get_state(),
|
||||
price = price_column.filter.get_state(),
|
||||
qty_filled = filled_qty_column.filter.get_state(),
|
||||
filled_notional_value = filled_notional_value_column.filter.get_state(),
|
||||
avg_price = avg_price_column.filter.get_state(),
|
||||
last_price = last_price_column.filter.get_state(),
|
||||
statuses = status_column.filter.get_state(),
|
||||
@@ -427,9 +415,7 @@ namespace mbts.spark {
|
||||
account_column.filter.load_state(fs.accounts);
|
||||
price_column.filter.load_state(fs.price);
|
||||
qty_column.filter.load_state(fs.qty);
|
||||
notional_value_column.filter.load_state(fs.notional_value);
|
||||
filled_qty_column.filter.load_state(fs.qty_filled);
|
||||
filled_notional_value_column.filter.load_state(fs.filled_notional_value);
|
||||
side_column.filter.load_state(fs.sides);
|
||||
order_type_column.filter.load_state(fs.order_types);
|
||||
root_algo_type_column.filter.load_state(fs.root_algo_types);
|
||||
@@ -659,32 +645,6 @@ namespace mbts.spark {
|
||||
DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class NotionalValueColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public NotionalValueColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
|
||||
: base(
|
||||
Columns.NotionalValue,
|
||||
DefaultColumnHeaders.NotionalValue,
|
||||
r => r.notional_value_text,
|
||||
DefaultColumnWidths.NotionalValue,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.notional_value)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(
|
||||
dc,
|
||||
_get_glyph_context(row),
|
||||
font_size,
|
||||
row.notional_value_text,
|
||||
position,
|
||||
row_height,
|
||||
RenderUtils.get_side_brush(row),
|
||||
background_brush,
|
||||
horizontal_alignment,
|
||||
VerticalAlignment.Center,
|
||||
DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
|
||||
: base(Columns.Filled, "Filled Qty", r => r.display_filled_quantity.ToString(), DefaultColumnWidths.Qty, filter, DataGridFilterKnownTemplates.DoubleMinMax, _wrap_comparer(r => r.filled_quantity)) { }
|
||||
@@ -704,32 +664,6 @@ namespace mbts.spark {
|
||||
DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class FilledNotionalValueColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledNotionalValueColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
|
||||
: base(
|
||||
Columns.FilledNotionalValue,
|
||||
DefaultColumnHeaders.FilledNotionalValue,
|
||||
r => r.filled_notional_value_text,
|
||||
DefaultColumnWidths.FilledNotionalValue,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.filled_notional_value)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(
|
||||
dc,
|
||||
_get_glyph_context(row),
|
||||
font_size,
|
||||
row.filled_notional_value_text,
|
||||
position,
|
||||
row_height,
|
||||
RenderUtils.get_side_brush(row),
|
||||
background_brush,
|
||||
horizontal_alignment,
|
||||
VerticalAlignment.Center,
|
||||
DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
|
||||
readonly BindingBase _foreground_binding;
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using bts;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
@@ -108,8 +108,6 @@ namespace mbts.spark {
|
||||
public double display_quantity => quantity.safe_divide(_display_qty_divisor);
|
||||
public string display_quantity_text =>
|
||||
quantity_display_settings.to_tick_display_settings().format(display_quantity);
|
||||
public double notional_value => order_tuple.notional_value;
|
||||
public string notional_value_text => _fmt_price(notional_value);
|
||||
public TickDisplaySettingsImmutable quantity_display_settings {
|
||||
get => _quantity_display_settings;
|
||||
set {
|
||||
@@ -130,8 +128,6 @@ namespace mbts.spark {
|
||||
filled_quantity.safe_divide(_display_qty_divisor);
|
||||
public string display_filled_quantity_text =>
|
||||
quantity_display_settings.to_tick_display_settings().format(display_filled_quantity);
|
||||
public double filled_notional_value => order_tuple.filled_notional_value;
|
||||
public string filled_notional_value_text => _fmt_price(filled_notional_value);
|
||||
public string last_msg => order.last_msg();
|
||||
// piq
|
||||
public uint piq => order_tuple.order.piq ?? uint.MaxValue;
|
||||
@@ -359,15 +355,9 @@ namespace mbts.spark {
|
||||
if (_prospective_qty_translator_backing != null)
|
||||
_prospective_qty_translator_backing.actual_value = quantity;
|
||||
}
|
||||
if ((Columns.NotionalValue & dirty) == Columns.NotionalValue) {
|
||||
RaisePropertyChanged(nameof(notional_value), nameof(notional_value_text));
|
||||
}
|
||||
if ((Columns.Filled & dirty) != 0) {
|
||||
RaisePropertyChanged(nameof(filled_quantity), nameof(display_filled_quantity));
|
||||
}
|
||||
if ((Columns.FilledNotionalValue & dirty) == Columns.FilledNotionalValue) {
|
||||
RaisePropertyChanged(nameof(filled_notional_value), nameof(filled_notional_value_text));
|
||||
}
|
||||
// badge fill
|
||||
if (old_order_tuple.order.meta?.badge_color != order_tuple.order.meta?.badge_color) {
|
||||
// invalidate by setting to null (will be re-instantiated in the public getter)
|
||||
@@ -531,12 +521,6 @@ namespace mbts.spark {
|
||||
if (oldval.order.piq != newval.order.piq) {
|
||||
dirty |= Columns.Piq;
|
||||
}
|
||||
if (oldval.notional_value != newval.notional_value) {
|
||||
dirty |= Columns.NotionalValue;
|
||||
}
|
||||
if (oldval.filled_notional_value != newval.filled_notional_value) {
|
||||
dirty |= Columns.FilledNotionalValue;
|
||||
}
|
||||
return dirty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,7 +389,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void refresh_quantity_display() {
|
||||
foreach (var row in _row_collection.GetAllRows()) {
|
||||
foreach (var row in _row_collection.all_rows) {
|
||||
if (_cached_securities.TryGetValue(row.security_key, out var smd)) {
|
||||
row.quantity_display_settings = smd.quantity_display;
|
||||
}
|
||||
|
||||
@@ -1,170 +1,160 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Orders;
|
||||
|
||||
using System;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
[ProtoContract]
|
||||
class FilterStates : IEquatable<FilterStates> {
|
||||
[ProtoMember(1)]
|
||||
public SecurityCriteria security_criteria;
|
||||
[ProtoMember(2)]
|
||||
public string order_key_regex;
|
||||
[ProtoMember(3)]
|
||||
public TimeFilterTuple time_created;
|
||||
[ProtoMember(4)]
|
||||
public string[] users;
|
||||
[ProtoMember(5)]
|
||||
public string[] accounts;
|
||||
[ProtoMember(6)]
|
||||
public Venue[] exchanges;
|
||||
[ProtoMember(7)]
|
||||
public TimeInForce[] tifs;
|
||||
//[ProtoMember(8)]
|
||||
//public CustomOrderDefinition.CustomOneofCase[] order_types;
|
||||
[ProtoMember(23)]
|
||||
public AlgoID[] order_types;
|
||||
[ProtoMember(24)]
|
||||
public AlgoID[] root_algo_types;
|
||||
[ProtoMember(9)]
|
||||
public Side[] sides;
|
||||
[ProtoMember(10)]
|
||||
public DoubleMinMaxState qty;
|
||||
[ProtoMember(26)]
|
||||
public DoubleMinMaxState notional_value;
|
||||
[ProtoMember(11)]
|
||||
public DoubleMinMaxState price;
|
||||
[ProtoMember(12)]
|
||||
public DoubleMinMaxState qty_filled;
|
||||
[ProtoMember(27)]
|
||||
public DoubleMinMaxState filled_notional_value;
|
||||
[ProtoMember(13)]
|
||||
public DoubleMinMaxState avg_price;
|
||||
[ProtoMember(14)]
|
||||
public DoubleMinMaxState last_price;
|
||||
[ProtoMember(15)]
|
||||
public OrderStatus[] statuses;
|
||||
[ProtoMember(16)]
|
||||
public string exchange_message_regex;
|
||||
[ProtoMember(17)]
|
||||
public TimeFilterTuple last_change;
|
||||
//[ProtoMember(18)]
|
||||
//public string security_regex;
|
||||
[ProtoMember(19)]
|
||||
public api.orderman.OrderSubType[] order_sub_types;
|
||||
[ProtoMember(20)]
|
||||
public string[] symbols;
|
||||
[ProtoMember(21)]
|
||||
public string parent_key_regex;
|
||||
[ProtoMember(22)]
|
||||
public string attached_target_order_key_regex;
|
||||
[ProtoMember(25)]
|
||||
public UIntMinMaxState piq;
|
||||
|
||||
public bool Equals(FilterStates other) =>
|
||||
other != null &&
|
||||
security_criteria.safe_equals(other.security_criteria) &&
|
||||
order_key_regex == other.order_key_regex &&
|
||||
parent_key_regex == other.parent_key_regex &&
|
||||
attached_target_order_key_regex == other.attached_target_order_key_regex &&
|
||||
time_created.Equals(other.time_created) &&
|
||||
EqualityComparer.safe_sequence_equals(users, other.users) &&
|
||||
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
|
||||
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
|
||||
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
|
||||
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
|
||||
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
|
||||
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
|
||||
qty.safe_equals(other.qty) &&
|
||||
notional_value.safe_equals(other.notional_value) &&
|
||||
price.safe_equals(other.price) &&
|
||||
qty_filled.safe_equals(other.qty_filled) &&
|
||||
filled_notional_value.safe_equals(other.filled_notional_value) &&
|
||||
avg_price.safe_equals(other.avg_price) &&
|
||||
last_price.safe_equals(other.last_price) &&
|
||||
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
|
||||
exchange_message_regex == other.exchange_message_regex &&
|
||||
last_change.Equals(other.last_change) &&
|
||||
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
|
||||
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
|
||||
piq.safe_equals(other.piq);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FilterStates);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
security_criteria?.GetHashCode() ?? 0,
|
||||
order_key_regex?.GetHashCode() ?? 0,
|
||||
parent_key_regex?.GetHashCode() ?? 0,
|
||||
attached_target_order_key_regex?.GetHashCode() ?? 0,
|
||||
time_created.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(users),
|
||||
EqualityComparer.safe_aggregate_hash(accounts),
|
||||
EqualityComparer.safe_aggregate_hash(exchanges),
|
||||
EqualityComparer.safe_aggregate_hash(tifs),
|
||||
EqualityComparer.safe_aggregate_hash(order_types),
|
||||
EqualityComparer.safe_aggregate_hash(root_algo_types),
|
||||
EqualityComparer.safe_aggregate_hash(sides),
|
||||
qty?.GetHashCode() ?? 0,
|
||||
notional_value?.GetHashCode() ?? 0,
|
||||
price?.GetHashCode() ?? 0,
|
||||
qty_filled?.GetHashCode() ?? 0,
|
||||
filled_notional_value?.GetHashCode() ?? 0,
|
||||
avg_price?.GetHashCode() ?? 0,
|
||||
last_price?.GetHashCode() ?? 0,
|
||||
EqualityComparer.safe_aggregate_hash(statuses),
|
||||
exchange_message_regex?.GetHashCode() ?? 0,
|
||||
last_change.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(order_sub_types),
|
||||
EqualityComparer.safe_aggregate_hash(symbols),
|
||||
piq?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderRecordTupleWithRoot, bool> build_predicate(this FilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<OrderRecordTupleWithRoot>();
|
||||
// security
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.order.security);
|
||||
// regex
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.order.ParentKey.ToString());
|
||||
// time
|
||||
pb.add_time_filter(fs.time_created, o => o.order.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.order.last_change());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.users, o => o.order.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.order.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.order.Def.CustomDef));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root.algo?.key ?? default));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.order.quantity());
|
||||
pb.add_numeric(fs.notional_value, o => o.notional_value);
|
||||
pb.add_price(fs.price, o => o.order.order.ui_price(), o => o.order.security);
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.order.filled_quantity());
|
||||
pb.add_numeric(fs.filled_notional_value, o => o.filled_notional_value);
|
||||
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.order.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Orders;
|
||||
|
||||
using System;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
[ProtoContract]
|
||||
class FilterStates : IEquatable<FilterStates> {
|
||||
[ProtoMember(1)]
|
||||
public SecurityCriteria security_criteria;
|
||||
[ProtoMember(2)]
|
||||
public string order_key_regex;
|
||||
[ProtoMember(3)]
|
||||
public TimeFilterTuple time_created;
|
||||
[ProtoMember(4)]
|
||||
public string[] users;
|
||||
[ProtoMember(5)]
|
||||
public string[] accounts;
|
||||
[ProtoMember(6)]
|
||||
public Venue[] exchanges;
|
||||
[ProtoMember(7)]
|
||||
public TimeInForce[] tifs;
|
||||
//[ProtoMember(8)]
|
||||
//public CustomOrderDefinition.CustomOneofCase[] order_types;
|
||||
[ProtoMember(23)]
|
||||
public AlgoID[] order_types;
|
||||
[ProtoMember(24)]
|
||||
public AlgoID[] root_algo_types;
|
||||
[ProtoMember(9)]
|
||||
public Side[] sides;
|
||||
[ProtoMember(10)]
|
||||
public DoubleMinMaxState qty;
|
||||
[ProtoMember(11)]
|
||||
public DoubleMinMaxState price;
|
||||
[ProtoMember(12)]
|
||||
public DoubleMinMaxState qty_filled;
|
||||
[ProtoMember(13)]
|
||||
public DoubleMinMaxState avg_price;
|
||||
[ProtoMember(14)]
|
||||
public DoubleMinMaxState last_price;
|
||||
[ProtoMember(15)]
|
||||
public OrderStatus[] statuses;
|
||||
[ProtoMember(16)]
|
||||
public string exchange_message_regex;
|
||||
[ProtoMember(17)]
|
||||
public TimeFilterTuple last_change;
|
||||
//[ProtoMember(18)]
|
||||
//public string security_regex;
|
||||
[ProtoMember(19)]
|
||||
public api.orderman.OrderSubType[] order_sub_types;
|
||||
[ProtoMember(20)]
|
||||
public string[] symbols;
|
||||
[ProtoMember(21)]
|
||||
public string parent_key_regex;
|
||||
[ProtoMember(22)]
|
||||
public string attached_target_order_key_regex;
|
||||
[ProtoMember(25)]
|
||||
public UIntMinMaxState piq;
|
||||
|
||||
public bool Equals(FilterStates other) =>
|
||||
other != null &&
|
||||
security_criteria.safe_equals(other.security_criteria) &&
|
||||
order_key_regex == other.order_key_regex &&
|
||||
parent_key_regex == other.parent_key_regex &&
|
||||
attached_target_order_key_regex == other.attached_target_order_key_regex &&
|
||||
time_created.Equals(other.time_created) &&
|
||||
EqualityComparer.safe_sequence_equals(users, other.users) &&
|
||||
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
|
||||
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
|
||||
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
|
||||
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
|
||||
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
|
||||
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
|
||||
qty.safe_equals(other.qty) &&
|
||||
price.safe_equals(other.price) &&
|
||||
qty_filled.safe_equals(other.qty_filled) &&
|
||||
avg_price.safe_equals(other.avg_price) &&
|
||||
last_price.safe_equals(other.last_price) &&
|
||||
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
|
||||
exchange_message_regex == other.exchange_message_regex &&
|
||||
last_change.Equals(other.last_change) &&
|
||||
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
|
||||
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
|
||||
piq.safe_equals(other.piq);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FilterStates);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
security_criteria?.GetHashCode() ?? 0,
|
||||
order_key_regex?.GetHashCode() ?? 0,
|
||||
parent_key_regex?.GetHashCode() ?? 0,
|
||||
attached_target_order_key_regex?.GetHashCode() ?? 0,
|
||||
time_created.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(users),
|
||||
EqualityComparer.safe_aggregate_hash(accounts),
|
||||
EqualityComparer.safe_aggregate_hash(exchanges),
|
||||
EqualityComparer.safe_aggregate_hash(tifs),
|
||||
EqualityComparer.safe_aggregate_hash(order_types),
|
||||
EqualityComparer.safe_aggregate_hash(root_algo_types),
|
||||
EqualityComparer.safe_aggregate_hash(sides),
|
||||
qty?.GetHashCode() ?? 0,
|
||||
price?.GetHashCode() ?? 0,
|
||||
qty_filled?.GetHashCode() ?? 0,
|
||||
avg_price?.GetHashCode() ?? 0,
|
||||
last_price?.GetHashCode() ?? 0,
|
||||
EqualityComparer.safe_aggregate_hash(statuses),
|
||||
exchange_message_regex?.GetHashCode() ?? 0,
|
||||
last_change.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(order_sub_types),
|
||||
EqualityComparer.safe_aggregate_hash(symbols),
|
||||
piq?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderRecordTupleWithRoot, bool> build_predicate(this FilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<OrderRecordTupleWithRoot>();
|
||||
// security
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.order.security);
|
||||
// regex
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.order.ParentKey.ToString());
|
||||
// time
|
||||
pb.add_time_filter(fs.time_created, o => o.order.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.order.last_change());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.users, o => o.order.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.order.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.order.Def.CustomDef));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root.algo?.key ?? default));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.order.quantity());
|
||||
pb.add_price(fs.price, o => o.order.order.ui_price(), o => o.order.security);
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.order.filled_quantity());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.order.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace bts.shell.orderbook {
|
||||
static OrderBookGridSettingsManager() {
|
||||
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
|
||||
// TODO: Remove the immutable font-weight flags when cell animation is ready
|
||||
var col_settings_flags = Enum.GetValuesAsUnderlyingType<Columns>().Cast<Columns>().ToDictionary(
|
||||
var col_settings_flags = EnumHelper.get_values<Columns>().ToDictionary(
|
||||
c => c.ToString(), c => GridColumnSettingsFeatureFlags.ImmutableFontWeight);
|
||||
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
// TODO: Remove the immutable font-color flags when cell animation is ready
|
||||
|
||||
@@ -1,277 +1,270 @@
|
||||
using bts.api.orderman;
|
||||
using bts.shell.fills;
|
||||
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using GridSettings = mbts.ui.settings.GridSettings;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
using ColHeader = mbts.ui.controls.DIYGrid.DefaultColumnHeaders;
|
||||
using ColWidth = mbts.ui.controls.DIYGrid.DefaultColumnWidths;
|
||||
|
||||
[ProtoContract]
|
||||
class OrderBookStartupData {
|
||||
const bool
|
||||
_default_show_right_panel = true,
|
||||
_default_show_order_params = true;
|
||||
const double
|
||||
_default_right_panel_width = 400,
|
||||
_default_order_params_height = 250,
|
||||
_default_fills_height = 300,
|
||||
_default_ladder_height = 350;
|
||||
[ProtoMember(16)]
|
||||
OrderBookRightPanelSettings _right_panel;
|
||||
// ALREADY USED TAGS
|
||||
// [ProtoMember(5)]
|
||||
// public readonly List<SortingCriteria> sort_criteria;
|
||||
|
||||
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
|
||||
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
|
||||
// right panel (host)
|
||||
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
|
||||
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
|
||||
// order params
|
||||
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
|
||||
public double order_params_height { get; set; }
|
||||
// ladder
|
||||
public bool show_ladder { get; set; }
|
||||
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
|
||||
// fills
|
||||
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
|
||||
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
|
||||
|
||||
// filters
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
// grid
|
||||
[ProtoMember(15)]
|
||||
[Obsolete]
|
||||
public GridSettings grid { get; set; }
|
||||
// orderbook grid
|
||||
[ProtoMember(17)]
|
||||
public OrderBookGridSettings orderbook_grid { get; set; }
|
||||
// fills settings
|
||||
[ProtoMember(26)]
|
||||
public OrderBookFillsGridSettings fills_settings { get; set; }
|
||||
// predicate animations
|
||||
[ProtoMember(20)]
|
||||
[Obsolete]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
// use defaults
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
// menu display
|
||||
[ProtoMember(27)]
|
||||
public SparkMenuDisplaySettings menu_display { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public OrderBookStartupData() {
|
||||
show_right_panel = _default_show_right_panel;
|
||||
show_order_params = _default_show_order_params;
|
||||
right_panel_width = _default_right_panel_width;
|
||||
order_params_height = _default_order_params_height;
|
||||
fills_height = _default_fills_height;
|
||||
ladder_height = _default_ladder_height;
|
||||
}
|
||||
|
||||
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
|
||||
this.filters = filters;
|
||||
this.use_defaults = use_defaults;
|
||||
this.show_right_panel = show_right_panel;
|
||||
this.right_panel_width = right_panel_width;
|
||||
this.fills_height = fills_height;
|
||||
this.show_fills = show_fills;
|
||||
this.orderbook_grid = orderbook_grid;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
_predicate_animations.AddRange(predicate_animations);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
[Obsolete]
|
||||
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
|
||||
public OrderBookGridSettings grid_settings_wrapper() {
|
||||
if (use_defaults)
|
||||
return null;
|
||||
if (orderbook_grid == null) {
|
||||
if (grid == null)
|
||||
grid = _default_general_grid;
|
||||
orderbook_grid = new OrderBookGridSettings(grid.Clone(), _predicate_animations);
|
||||
}
|
||||
return orderbook_grid;
|
||||
}
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public static OrderBookStartupData Default =>
|
||||
new OrderBookStartupData(
|
||||
new FilterStates { statuses = new[] { OrderStatus.Working } },
|
||||
DefaultGrid,
|
||||
_default_show_right_panel,
|
||||
_default_right_panel_width,
|
||||
_default_fills_height,
|
||||
use_defaults: true) {
|
||||
show_order_params = _default_show_order_params,
|
||||
order_params_height = _default_order_params_height,
|
||||
fills_settings = DefaultFillsGrid
|
||||
};
|
||||
|
||||
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
public static OrderBookFillsGridSettings DefaultFillsGrid {
|
||||
get {
|
||||
var default_fills = FillsSettings.Default();
|
||||
var rv = new OrderBookFillsGridSettings {
|
||||
sorting_criteria = default_fills.sorting_criteria.ToList(),
|
||||
filters = default_fills.filters,
|
||||
general = FillsSettings.MiniGridSettings
|
||||
};
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.standard, DefaultColumns.sorting_criteria);
|
||||
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criteria)) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
static class DefaultColumns {
|
||||
// standard
|
||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
|
||||
public static GridColumnSettings side => new(
|
||||
Columns.Side.ToString(), ColHeader.Side,
|
||||
width: ColWidth.Side,
|
||||
horizontal_alignment: HorizontalAlignment.Left);
|
||||
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings notional_value => new GridColumnSettings(Columns.NotionalValue.ToString(), DIYGrid.DefaultColumnHeaders.NotionalValue, width: DIYGrid.DefaultColumnWidths.NotionalValue, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings filled_quantity => new(
|
||||
Columns.Filled.ToString(),
|
||||
ColHeader.FilledQuantity,
|
||||
width: ColWidth.Filled,
|
||||
horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings filled_notional_value => new(
|
||||
Columns.FilledNotionalValue.ToString(),
|
||||
ColHeader.FilledNotionalValue,
|
||||
width: ColWidth.FilledNotionalValue,
|
||||
horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings avg_price => new(
|
||||
Columns.AvgFilled.ToString(),
|
||||
ColHeader.AvgFilled,
|
||||
width: ColWidth.AveragePrice,
|
||||
horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// editable
|
||||
public static GridColumnSettings cancel => new GridColumnSettings(Columns.Cancel.ToString(), DIYGrid.DefaultColumnHeaders.Cancel, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Cancel);
|
||||
public static GridColumnSettings attach_detach => new GridColumnSettings(Columns.Attach.ToString(), DIYGrid.DefaultColumnHeaders.AttachDetach, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.AttachDetach);
|
||||
public static GridColumnSettings update => new GridColumnSettings(Columns.Update.ToString(), DIYGrid.DefaultColumnHeaders.Update, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Update);
|
||||
public static GridColumnSettings edit_quantity => new GridColumnSettings(Columns.EditableQty.ToString(), DIYGrid.DefaultColumnHeaders.Qty, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditableSize);
|
||||
public static GridColumnSettings edit_price => new GridColumnSettings(Columns.EditablePrice.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditablePrice);
|
||||
public static GridColumnSettings commit => new GridColumnSettings(Columns.Commit.ToString(), width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Commit);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criteria {
|
||||
get {
|
||||
yield return new SortingCriteria(time_created.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> standard {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
yield return time_created;
|
||||
yield return last_change;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return root_algo_type;
|
||||
yield return sub_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return piq;
|
||||
yield return filled_quantity;
|
||||
yield return avg_price;
|
||||
yield return last_price;
|
||||
yield return status;
|
||||
yield return last_message;
|
||||
yield return security_key;
|
||||
yield return order_key;
|
||||
yield return parent_key;
|
||||
yield return attached_target_order_key;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> editable {
|
||||
get {
|
||||
yield return cancel;
|
||||
yield return update;
|
||||
yield return edit_quantity;
|
||||
yield return edit_price;
|
||||
yield return commit;
|
||||
yield return attach_detach;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
|
||||
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> mini {
|
||||
get {
|
||||
foreach (var col_setting in editable)
|
||||
yield return col_setting;
|
||||
foreach (var col_setting in standard) {
|
||||
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
|
||||
yield return col_setting.with(is_visible: false);
|
||||
} else {
|
||||
yield return col_setting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api.orderman;
|
||||
using bts.shell.fills;
|
||||
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using GridSettings = mbts.ui.settings.GridSettings;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
using ColHeader = mbts.ui.controls.DIYGrid.DefaultColumnHeaders;
|
||||
using ColWidth = mbts.ui.controls.DIYGrid.DefaultColumnWidths;
|
||||
|
||||
[ProtoContract]
|
||||
class OrderBookStartupData {
|
||||
const bool
|
||||
_default_show_right_panel = true,
|
||||
_default_show_order_params = true;
|
||||
const double
|
||||
_default_right_panel_width = 400,
|
||||
_default_order_params_height = 250,
|
||||
_default_fills_height = 300,
|
||||
_default_ladder_height = 350;
|
||||
[ProtoMember(16)]
|
||||
OrderBookRightPanelSettings _right_panel;
|
||||
// ALREADY USED TAGS
|
||||
// [ProtoMember(5)]
|
||||
// public readonly List<SortingCriteria> sort_criteria;
|
||||
|
||||
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
|
||||
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
|
||||
// right panel (host)
|
||||
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
|
||||
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
|
||||
// order params
|
||||
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
|
||||
public double order_params_height { get; set; }
|
||||
// ladder
|
||||
public bool show_ladder { get; set; }
|
||||
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
|
||||
// fills
|
||||
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
|
||||
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
|
||||
|
||||
// filters
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
// grid
|
||||
[ProtoMember(15)]
|
||||
[Obsolete]
|
||||
public GridSettings grid { get; set; }
|
||||
// orderbook grid
|
||||
[ProtoMember(17)]
|
||||
public OrderBookGridSettings orderbook_grid { get; set; }
|
||||
// fills settings
|
||||
[ProtoMember(26)]
|
||||
public OrderBookFillsGridSettings fills_settings { get; set; }
|
||||
// predicate animations
|
||||
[ProtoMember(20)]
|
||||
[Obsolete]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
// use defaults
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
// menu display
|
||||
[ProtoMember(27)]
|
||||
public SparkMenuDisplaySettings menu_display { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public OrderBookStartupData() {
|
||||
show_right_panel = _default_show_right_panel;
|
||||
show_order_params = _default_show_order_params;
|
||||
right_panel_width = _default_right_panel_width;
|
||||
order_params_height = _default_order_params_height;
|
||||
fills_height = _default_fills_height;
|
||||
ladder_height = _default_ladder_height;
|
||||
}
|
||||
|
||||
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
|
||||
this.filters = filters;
|
||||
this.use_defaults = use_defaults;
|
||||
this.show_right_panel = show_right_panel;
|
||||
this.right_panel_width = right_panel_width;
|
||||
this.fills_height = fills_height;
|
||||
this.show_fills = show_fills;
|
||||
this.orderbook_grid = orderbook_grid;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
_predicate_animations.AddRange(predicate_animations);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
[Obsolete]
|
||||
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
|
||||
public OrderBookGridSettings grid_settings_wrapper() {
|
||||
if (use_defaults)
|
||||
return null;
|
||||
if (orderbook_grid == null) {
|
||||
if (grid == null)
|
||||
grid = _default_general_grid;
|
||||
orderbook_grid = new OrderBookGridSettings(grid.Clone(), _predicate_animations);
|
||||
}
|
||||
return orderbook_grid;
|
||||
}
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public static OrderBookStartupData Default =>
|
||||
new OrderBookStartupData(
|
||||
new FilterStates { statuses = new[] { OrderStatus.Working } },
|
||||
DefaultGrid,
|
||||
_default_show_right_panel,
|
||||
_default_right_panel_width,
|
||||
_default_fills_height,
|
||||
use_defaults: true) {
|
||||
show_order_params = _default_show_order_params,
|
||||
order_params_height = _default_order_params_height,
|
||||
fills_settings = DefaultFillsGrid
|
||||
};
|
||||
|
||||
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
public static OrderBookFillsGridSettings DefaultFillsGrid {
|
||||
get {
|
||||
var default_fills = FillsSettings.Default();
|
||||
var rv = new OrderBookFillsGridSettings {
|
||||
sorting_criteria = default_fills.sorting_criteria.ToList(),
|
||||
filters = default_fills.filters,
|
||||
general = FillsSettings.MiniGridSettings
|
||||
};
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.standard, DefaultColumns.sorting_criteria);
|
||||
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criteria)) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
static class DefaultColumns {
|
||||
// standard
|
||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
|
||||
public static GridColumnSettings side => new(
|
||||
Columns.Side.ToString(), ColHeader.Side,
|
||||
width: ColWidth.Side,
|
||||
horizontal_alignment: HorizontalAlignment.Left);
|
||||
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings filled_quantity => new(
|
||||
Columns.Filled.ToString(),
|
||||
ColHeader.FilledQuantity,
|
||||
width: ColWidth.Filled,
|
||||
horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings avg_price => new(
|
||||
Columns.AvgFilled.ToString(),
|
||||
ColHeader.AvgFilled,
|
||||
width: ColWidth.AveragePrice,
|
||||
horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// editable
|
||||
public static GridColumnSettings cancel => new GridColumnSettings(Columns.Cancel.ToString(), DIYGrid.DefaultColumnHeaders.Cancel, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Cancel);
|
||||
public static GridColumnSettings attach_detach => new GridColumnSettings(Columns.Attach.ToString(), DIYGrid.DefaultColumnHeaders.AttachDetach, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.AttachDetach);
|
||||
public static GridColumnSettings update => new GridColumnSettings(Columns.Update.ToString(), DIYGrid.DefaultColumnHeaders.Update, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Update);
|
||||
public static GridColumnSettings edit_quantity => new GridColumnSettings(Columns.EditableQty.ToString(), DIYGrid.DefaultColumnHeaders.Qty, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditableSize);
|
||||
public static GridColumnSettings edit_price => new GridColumnSettings(Columns.EditablePrice.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditablePrice);
|
||||
public static GridColumnSettings commit => new GridColumnSettings(Columns.Commit.ToString(), width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Commit);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criteria {
|
||||
get {
|
||||
yield return new SortingCriteria(time_created.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> standard {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
yield return time_created;
|
||||
yield return last_change;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return root_algo_type;
|
||||
yield return sub_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return piq;
|
||||
yield return filled_quantity;
|
||||
yield return avg_price;
|
||||
yield return last_price;
|
||||
yield return status;
|
||||
yield return last_message;
|
||||
yield return security_key;
|
||||
yield return order_key;
|
||||
yield return parent_key;
|
||||
yield return attached_target_order_key;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> editable {
|
||||
get {
|
||||
yield return cancel;
|
||||
yield return update;
|
||||
yield return edit_quantity;
|
||||
yield return edit_price;
|
||||
yield return commit;
|
||||
yield return attach_detach;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
|
||||
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> mini {
|
||||
get {
|
||||
foreach (var col_setting in editable)
|
||||
yield return col_setting;
|
||||
foreach (var col_setting in standard) {
|
||||
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
|
||||
yield return col_setting.with(is_visible: false);
|
||||
} else {
|
||||
yield return col_setting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +1,107 @@
|
||||
using bts.api.orderman;
|
||||
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal partial class OrderBookGridView : DIYGrid {
|
||||
public const string
|
||||
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
|
||||
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
|
||||
RowStyleKey = "RowStyle",
|
||||
RowTemplateKey = "RowTemplateKey",
|
||||
CVRowStyleKey = "CVRowStyle",
|
||||
CVRowTemplateKey = "CVRowTemplateKey";
|
||||
|
||||
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
|
||||
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
|
||||
public enum ColumnKeys : long {
|
||||
Security = 1,
|
||||
Symbol = 1 << 1,
|
||||
Time = 1 << 2,
|
||||
LastChange = 1 << 3,
|
||||
User = 1 << 4,
|
||||
Account = 1 << 5,
|
||||
Exchange = 1 << 6,
|
||||
TimeInForce = 1 << 7,
|
||||
OrderType = 1 << 8,
|
||||
SubType = 1 << 9,
|
||||
Side = 1 << 10,
|
||||
Size = 1 << 11,
|
||||
Price = 1 << 12,
|
||||
Filled = 1 << 13,
|
||||
AvgFilled = 1 << 14,
|
||||
LastPrice = 1 << 15,
|
||||
Status = 1 << 16,
|
||||
LastMessage = 1 << 17,
|
||||
SecurityKey = 1 << 18,
|
||||
OrderKey = 1 << 19,
|
||||
ParentKey = 1 << 20,
|
||||
AttachedTargetOrderKey = 1 << 21,
|
||||
Cancel = 1 << 22,
|
||||
EditableQty = 1 << 23,
|
||||
EditablePrice = 1 << 24,
|
||||
Attach = 1 << 25,
|
||||
Detach = 1 << 26,
|
||||
Update = 1 << 27,
|
||||
Commit = 1 << 28,
|
||||
RootAlgoType = 1 << 29,
|
||||
Piq = 1 << 30,
|
||||
Check = 1 << 31,
|
||||
NotionalValue = 1L << 32,
|
||||
FilledNotionalValue = 1L << 33,
|
||||
}
|
||||
|
||||
public OrderBookGridView() {
|
||||
InitializeComponent();
|
||||
var row_template_factory = new FrameworkElementFactory(typeof(OrderBookRowView));
|
||||
row_template_factory.SetValue(OrderBookRowView.BackgroundProperty, RowBackgroundHelper.row_background_binding);
|
||||
RowTemplate = new DataTemplate { VisualTree = row_template_factory };
|
||||
}
|
||||
|
||||
static class RowBackgroundHelper {
|
||||
public static readonly BindingBase row_background_binding;
|
||||
|
||||
static RowBackgroundHelper() {
|
||||
var bg_binding = new MultiBinding();
|
||||
bg_binding.Bindings.Add(new Binding(nameof(OrderBookRowVM.status)));
|
||||
bg_binding.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
|
||||
double overlay_opacity = config.Configuration.get<bts.config.OrderBookOptions>().StatusColorOpacityWhenSelected;
|
||||
if (overlay_opacity > 0.1 && overlay_opacity < 0.9) {
|
||||
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
|
||||
var color = _status_color(status);
|
||||
return color.HasValue ? new SolidColorBrush(color.Value) { Opacity = is_selected ? overlay_opacity : 1.0 } : null;
|
||||
});
|
||||
} else {
|
||||
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
|
||||
if (is_selected)
|
||||
return null;
|
||||
var color = _status_color(status);
|
||||
return color.HasValue ? new SolidColorBrush(color.Value) : null;
|
||||
});
|
||||
}
|
||||
row_background_binding = bg_binding;
|
||||
}
|
||||
|
||||
static Color? _status_color(OrderStatus status) {
|
||||
switch (status) {
|
||||
case OrderStatus.Canceled: return BTSColors.Orders.canceled_order_background;
|
||||
case OrderStatus.Completed: return BTSColors.Orders.completed_order_background;
|
||||
case OrderStatus.Disconnected:
|
||||
case OrderStatus.RejectedBySpark:
|
||||
case OrderStatus.Error:
|
||||
return BTSColors.Orders.error_order_background;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
|
||||
public OrderBookRowView() : base() { }
|
||||
}
|
||||
}
|
||||
using bts.api.orderman;
|
||||
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal partial class OrderBookGridView : DIYGrid {
|
||||
public const string
|
||||
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
|
||||
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
|
||||
RowStyleKey = "RowStyle",
|
||||
RowTemplateKey = "RowTemplateKey",
|
||||
CVRowStyleKey = "CVRowStyle",
|
||||
CVRowTemplateKey = "CVRowTemplateKey";
|
||||
|
||||
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
|
||||
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
|
||||
public enum ColumnKeys {
|
||||
Security = 1,
|
||||
Symbol = 1 << 1,
|
||||
Time = 1 << 2,
|
||||
LastChange = 1 << 3,
|
||||
User = 1 << 4,
|
||||
Account = 1 << 5,
|
||||
Exchange = 1 << 6,
|
||||
TimeInForce = 1 << 7,
|
||||
OrderType = 1 << 8,
|
||||
SubType = 1 << 9,
|
||||
Side = 1 << 10,
|
||||
Size = 1 << 11,
|
||||
Price = 1 << 12,
|
||||
Filled = 1 << 13,
|
||||
AvgFilled = 1 << 14,
|
||||
LastPrice = 1 << 15,
|
||||
Status = 1 << 16,
|
||||
LastMessage = 1 << 17,
|
||||
SecurityKey = 1 << 18,
|
||||
OrderKey = 1 << 19,
|
||||
ParentKey = 1 << 20,
|
||||
AttachedTargetOrderKey = 1 << 21,
|
||||
Cancel = 1 << 22,
|
||||
EditableQty = 1 << 23,
|
||||
EditablePrice = 1 << 24,
|
||||
Attach = 1 << 25,
|
||||
Detach = 1 << 26,
|
||||
Update = 1 << 27,
|
||||
Commit = 1 << 28,
|
||||
RootAlgoType = 1 << 29,
|
||||
Piq = 1 << 30,
|
||||
Check = 1 << 31
|
||||
}
|
||||
|
||||
public OrderBookGridView() {
|
||||
InitializeComponent();
|
||||
var row_template_factory = new FrameworkElementFactory(typeof(OrderBookRowView));
|
||||
row_template_factory.SetValue(OrderBookRowView.BackgroundProperty, RowBackgroundHelper.row_background_binding);
|
||||
RowTemplate = new DataTemplate { VisualTree = row_template_factory };
|
||||
}
|
||||
|
||||
static class RowBackgroundHelper {
|
||||
public static readonly BindingBase row_background_binding;
|
||||
|
||||
static RowBackgroundHelper() {
|
||||
var bg_binding = new MultiBinding();
|
||||
bg_binding.Bindings.Add(new Binding(nameof(OrderBookRowVM.status)));
|
||||
bg_binding.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
|
||||
double overlay_opacity = config.Configuration.get<bts.config.OrderBookOptions>().StatusColorOpacityWhenSelected;
|
||||
if (overlay_opacity > 0.1 && overlay_opacity < 0.9) {
|
||||
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
|
||||
var color = _status_color(status);
|
||||
return color.HasValue ? new SolidColorBrush(color.Value) { Opacity = is_selected ? overlay_opacity : 1.0 } : null;
|
||||
});
|
||||
} else {
|
||||
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
|
||||
if (is_selected)
|
||||
return null;
|
||||
var color = _status_color(status);
|
||||
return color.HasValue ? new SolidColorBrush(color.Value) : null;
|
||||
});
|
||||
}
|
||||
row_background_binding = bg_binding;
|
||||
}
|
||||
|
||||
static Color? _status_color(OrderStatus status) {
|
||||
switch (status) {
|
||||
case OrderStatus.Canceled: return BTSColors.Orders.canceled_order_background;
|
||||
case OrderStatus.Completed: return BTSColors.Orders.completed_order_background;
|
||||
case OrderStatus.Disconnected:
|
||||
case OrderStatus.RejectedBySpark:
|
||||
case OrderStatus.Error:
|
||||
return BTSColors.Orders.error_order_background;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
|
||||
public OrderBookRowView() : base() { }
|
||||
}
|
||||
}
|
||||
@@ -1,380 +1,378 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
using Spark.Core.Collections;
|
||||
using Spark.Core.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
using PNLKey = bts.AccountSecurityKey;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLRowVM : DisposableViewModelBase, IEquatable<PNLRowVM> {
|
||||
public static readonly IComparer<PNLRowVM> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowVM>(r => r.key);
|
||||
static readonly Func<bool, string> _fmt_has_day_trades = hdt => hdt ? "Yes" : "No";
|
||||
//
|
||||
readonly PNLCellIndexBuilder<PNLRowData> _cell_map = new PNLCellIndexBuilder<PNLRowData>();
|
||||
readonly PNLCellEmbeddedAnimationModel<PNLRowData> _animator;
|
||||
readonly ISecurityMetadataStore _sec_meta_store;
|
||||
bool _refresh_normalized_data = true, _is_selected;
|
||||
PNLNormalizedData _normalized_data;
|
||||
api.domain.TickDisplaySettingsImmutable? _quantity_display_backing;
|
||||
|
||||
public PNLRowVM(
|
||||
PNLRowData data,
|
||||
ISecurityMetadataStore sec_meta_store,
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances,
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers,
|
||||
Func<double, string> fmt_fill_price,
|
||||
Func<double, string> fmt_market_price) {
|
||||
//
|
||||
this.data = data;
|
||||
_sec_meta_store = sec_meta_store;
|
||||
_animator = new PNLCellEmbeddedAnimationModel<PNLRowData>(default_appearances, triggers);
|
||||
_animator.on_row(data);
|
||||
pnl_price = _cell_map.add_price_double(ColumnKeys.PNLPrice, d => d.pnl_price);
|
||||
account_cell = _cell_map.add(ColumnKeys.Account, d => d.account);
|
||||
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
|
||||
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
|
||||
expiration_cell = _cell_map.add(
|
||||
ColumnKeys.Expiration,
|
||||
d => d.expiration,
|
||||
e => e == DateTime.MinValue
|
||||
? "N/A"
|
||||
: SecurityFormatter.format_expiration(e));
|
||||
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.symbol);
|
||||
security_cell = _cell_map.add(
|
||||
ColumnKeys.Security,
|
||||
d => d.security,
|
||||
null,
|
||||
EqualityComparer<SecurityImmutable?>.Default,
|
||||
Comparer<SecurityImmutable?>.Default);
|
||||
security_key = _cell_map.add(ColumnKeys.SecurityKey, d => d.key.security_id);
|
||||
cpp = _cell_map.add(ColumnKeys.CPP, d => d.effective_cpp);
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl_double(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.SODNetPos, d => d.display_sod_net_pos, fmt_qty);
|
||||
sod_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.SODLongPos, d => d.display_sod_long_pos, fmt_qty);
|
||||
sod_long_avg_price = _cell_map.add_long_price(ColumnKeys.SODLongAvgPrice, d => d.sod_long_avg_price);
|
||||
sod_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.SODShortPos, d => d.display_sod_short_pos, fmt_qty);
|
||||
sod_short_avg_price = _cell_map.add_short_price(ColumnKeys.SODShortAvgPrice, d => d.sod_short_avg_price);
|
||||
sod_delta = _cell_map.add_pnl_double(ColumnKeys.SODDelta, d => d.sod_delta, fmt_greek);
|
||||
sod_gamma = _cell_map.add_pnl_double(ColumnKeys.SODGamma, d => d.sod_gamma, fmt_greek);
|
||||
sod_vega = _cell_map.add_pnl_double(ColumnKeys.SODVega, d => d.sod_vega, fmt_greek);
|
||||
sod_theta = _cell_map.add_pnl_double(ColumnKeys.SODTheta, d => d.sod_theta, fmt_greek);
|
||||
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl_double(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.DayNetPos, d => d.display_day_net_pos, fmt_qty);
|
||||
day_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.DayLongPos, d => d.display_day_long_pos, fmt_qty);
|
||||
day_long_avg_price = _cell_map.add_long_price(ColumnKeys.DayLongAvgPrice, d => d.day_long_avg_price);
|
||||
day_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.DayShortPos, d => d.display_day_short_pos, fmt_qty);
|
||||
day_short_avg_price = _cell_map.add_short_price(ColumnKeys.DayShortAvgPrice, d => d.day_short_avg_price);
|
||||
day_delta = _cell_map.add_pnl_double(ColumnKeys.DayDelta, d => d.day_delta, fmt_greek);
|
||||
day_gamma = _cell_map.add_pnl_double(ColumnKeys.DayGamma, d => d.day_gamma, fmt_greek);
|
||||
day_vega = _cell_map.add_pnl_double(ColumnKeys.DayVega, d => d.day_vega, fmt_greek);
|
||||
day_theta = _cell_map.add_pnl_double(ColumnKeys.DayTheta, d => d.day_theta, fmt_greek);
|
||||
has_day_trades = _cell_map.add(ColumnKeys.HasDayTrades, d => d.has_day_trades, _fmt_has_day_trades);
|
||||
// working
|
||||
working_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.WorkingNetPos, d => d.display_working_net_pos, fmt_qty);
|
||||
var qty_display = _sec_meta_store.get_qty_display(security);
|
||||
working_long_pos = _cell_map.add_working_long(
|
||||
ColumnKeys.WorkingLongPos,
|
||||
d => d.working.long_data,
|
||||
d => d.bid_price,
|
||||
qty_display);
|
||||
working_long_avg_price = _cell_map.add_long_price(ColumnKeys.WorkingLongAvgPrice, d => d.working_long_avg_price);
|
||||
working_short_pos = _cell_map.add_working_short(
|
||||
ColumnKeys.WorkingShortPos, r => r.working.short_data, r => r.ask_price, qty_display);
|
||||
working_short_avg_price = _cell_map.add_short_price(ColumnKeys.WorkingShortAvgPrice, d => d.working_short_avg_price);
|
||||
// total
|
||||
total_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalPNL, d => d.total_pnl);
|
||||
total_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.TotalNetPos, d => d.display_total_net_pos, fmt_qty);
|
||||
total_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.TotalLongPos, d => d.display_total_long_pos, fmt_qty);
|
||||
total_long_avg_price = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPrice, d => d.total_long_avg_price);
|
||||
total_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.TotalShortPos, d => d.display_total_short_pos, fmt_qty);
|
||||
total_short_avg_price = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPrice, d => d.total_short_avg_price);
|
||||
total_delta = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalDelta, d => d.total_delta, fmt_greek);
|
||||
total_gamma = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalGamma, d => d.total_gamma, fmt_greek);
|
||||
total_vega = _cell_map.add_pnl_double(ColumnKeys.TotalVega, d => d.total_vega, fmt_greek);
|
||||
total_theta = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalTheta, d => d.total_theta, fmt_greek);
|
||||
|
||||
// market
|
||||
bid_price = _cell_map.add_price_double(ColumnKeys.BidPrice, d => d.bid_price);
|
||||
ask_price = _cell_map.add_price_double(ColumnKeys.AskPrice, d => d.ask_price);
|
||||
mid_price = _cell_map.add_price_double(ColumnKeys.MidPrice, d => d.mid_price);
|
||||
last_price = _cell_map.add_price_double(ColumnKeys.LastPrice, d => d.last_price);
|
||||
settlement_price = _cell_map.add_price_double(ColumnKeys.SettlementPrice, d => d.settlement_price);
|
||||
|
||||
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
|
||||
_cell_map.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
|
||||
animations = new_animations.Select(kv => new mbts.ui.spark.CellAnimation(kv.Key.column, kv.Key.type, kv.Value.color.to_brush())).ToList();
|
||||
RaisePropertyChanged(nameof(animations));
|
||||
});
|
||||
|
||||
static string fmt_greek(double v) =>
|
||||
v.is_nan() ? "-" : v.ToString("#,0.00");
|
||||
|
||||
string fmt_qty(double v) =>
|
||||
quantity_display.format(v);
|
||||
}
|
||||
|
||||
// events
|
||||
public event Action<PNLNormalizedData> updated;
|
||||
public event Action<bool> is_selected_changed;
|
||||
|
||||
// data
|
||||
public PNLKey key => data.key;
|
||||
public PNLRowData data { get; private set; }
|
||||
public PNLNormalizedData normalized_data {
|
||||
get {
|
||||
if (_refresh_normalized_data) {
|
||||
_refresh_normalized_data = false;
|
||||
// Only count outrights (spread positions should be normalized into outrights)
|
||||
var sec_type = security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
var (
|
||||
sod_long,
|
||||
sod_short,
|
||||
sod_delta,
|
||||
sod_gamma,
|
||||
sod_vega,
|
||||
sod_theta,
|
||||
day_long,
|
||||
day_short,
|
||||
day_delta,
|
||||
day_gamma,
|
||||
day_vega,
|
||||
day_theta) = sec_type switch {
|
||||
Security.ContractOneofCase.Future or Security.ContractOneofCase.Option => (
|
||||
data.sod.long_pos.round_to_decimal(),
|
||||
data.sod.short_pos.round_to_decimal(),
|
||||
data.sod.delta.round_to_decimal(),
|
||||
data.sod.gamma.round_to_decimal(),
|
||||
data.sod.vega.round_to_decimal(),
|
||||
data.sod.theta.round_to_decimal(),
|
||||
data.day.long_pos.round_to_decimal(),
|
||||
data.day.short_pos.round_to_decimal(),
|
||||
data.day.delta.round_to_decimal(),
|
||||
data.day.gamma.round_to_decimal(),
|
||||
data.day.vega.round_to_decimal(),
|
||||
data.day.theta.round_to_decimal()),
|
||||
_ => (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
};
|
||||
|
||||
var working_long = data.working_long_pos.round_to_decimal();
|
||||
var working_short = data.working_short_pos.round_to_decimal();
|
||||
|
||||
var nd = new PNLNormalizedData() {
|
||||
// sod
|
||||
sod_pnl = sod_pnl.value.round_to_decimal(),
|
||||
sod_long_pos = sod_long,
|
||||
sod_short_pos = sod_short,
|
||||
sod_delta = sod_delta,
|
||||
sod_gamma = sod_gamma,
|
||||
sod_vega = sod_vega,
|
||||
sod_theta = sod_theta,
|
||||
display_sod_long_pos = sod_long,
|
||||
display_sod_short_pos = sod_short,
|
||||
|
||||
// day
|
||||
day_pnl = day_pnl.value.round_to_decimal(),
|
||||
day_long_pos = day_long,
|
||||
day_short_pos = day_short,
|
||||
day_delta = day_delta,
|
||||
day_gamma = day_gamma,
|
||||
day_vega = day_vega,
|
||||
day_theta = day_theta,
|
||||
display_day_long_pos = day_long,
|
||||
display_day_short_pos = day_short,
|
||||
|
||||
// working
|
||||
working_long_pos = working_long,
|
||||
working_short_pos = working_short,
|
||||
display_working_long_pos = working_long,
|
||||
display_working_short_pos = working_short
|
||||
};
|
||||
if (_normalized_data != nd) {
|
||||
_normalized_data = nd;
|
||||
updated?.Invoke(nd);
|
||||
}
|
||||
}
|
||||
return _normalized_data;
|
||||
}
|
||||
}
|
||||
// security
|
||||
public string account_name => data.account_name;
|
||||
public Venue exchange => security?.Exchange ?? Venue.Unset;
|
||||
public string symbol => security.symbol_or_default();
|
||||
public DateTime expiration => data.expiration;
|
||||
public Security.ContractOneofCase security_type => security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
public SecurityImmutable? security => data.security;
|
||||
// cells
|
||||
public PNLCellVM<PNLRowData, AccountTuple> account_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Venue> exchange_cell { get; }
|
||||
public PNLCellVM<PNLRowData, DateTime> expiration_cell { get; }
|
||||
public PNLCellVM<PNLRowData, string> symbol_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Security.ContractOneofCase> security_type_cell { get; }
|
||||
public PNLCellVM<PNLRowData, SecurityImmutable?> security_cell { get; }
|
||||
public PNLCellVM<PNLRowData, uint> security_key { get; }
|
||||
public PNLCellVM<PNLRowData, double> cpp { get; }
|
||||
// sod
|
||||
public PNLCellVM<PNLRowData, double> sod_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_theta { get; }
|
||||
|
||||
// day
|
||||
public PNLCellVM<PNLRowData, double> day_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_theta { get; }
|
||||
public PNLCellVM<PNLRowData, bool> has_day_trades { get; }
|
||||
// working
|
||||
public PNLCellVM<PNLRowData, double> working_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_short_avg_price { get; }
|
||||
// total
|
||||
public PNLCellVM<PNLRowData, double> total_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_theta { get; }
|
||||
|
||||
// (market)
|
||||
public PNLCellVM<PNLRowData, double> bid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> mid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> ask_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> last_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> settlement_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> pnl_price { get; }
|
||||
|
||||
// display
|
||||
public api.domain.TickDisplaySettingsImmutable quantity_display {
|
||||
get {
|
||||
if (_quantity_display_backing.HasValue) {
|
||||
return _quantity_display_backing.Value;
|
||||
}
|
||||
if (security.HasValue) {
|
||||
_quantity_display_backing = _sec_meta_store.get_qty_display(security.Value);
|
||||
}
|
||||
return _quantity_display_backing ??= Security.Defaults.quantity_display_settingsi;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<mbts.ui.spark.CellAnimation> animations { get; private set; }
|
||||
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set {
|
||||
if (Set(ref _is_selected, value))
|
||||
is_selected_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<PNLCellVM<PNLRowData, double>> _market_price_cells {
|
||||
get {
|
||||
yield return pnl_price;
|
||||
yield return last_price;
|
||||
yield return bid_price;
|
||||
yield return mid_price;
|
||||
yield return ask_price;
|
||||
yield return settlement_price;
|
||||
}
|
||||
}
|
||||
|
||||
// includes sod, day, total, and working avg price columns
|
||||
IEnumerable<PNLCellVM<PNLRowData, double>> _fill_price_cells {
|
||||
get {
|
||||
// sod
|
||||
yield return sod_long_avg_price;
|
||||
yield return sod_short_avg_price;
|
||||
// day
|
||||
yield return day_long_avg_price;
|
||||
yield return day_short_avg_price;
|
||||
// total
|
||||
yield return total_long_avg_price;
|
||||
yield return total_short_avg_price;
|
||||
// working
|
||||
yield return working_long_avg_price;
|
||||
yield return working_short_avg_price;
|
||||
}
|
||||
}
|
||||
|
||||
public void update(PNLRowData data) {
|
||||
_quantity_display_backing = null;
|
||||
_refresh_normalized_data = true;
|
||||
this.data = data;
|
||||
_cell_map.on_row(data);
|
||||
_animator.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
}
|
||||
|
||||
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers, Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
|
||||
_animator.on_settings(defaults, triggers);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
|
||||
}
|
||||
|
||||
public bool Equals(PNLRowVM other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
$"account: {account_name} exchange: {exchange} symbol: {symbol} security: {security} buy_quantity: {day_long_pos} sell_quantity: {day_short_pos} pnl: {total_pnl}";
|
||||
|
||||
void _set_price_formatting_delegates(Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
|
||||
foreach (var cell in _fill_price_cells) {
|
||||
cell.format = fmt_fill_price;
|
||||
}
|
||||
foreach (var cell in _market_price_cells) {
|
||||
cell.format = fmt_market_price;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
using Spark.Core.Collections;
|
||||
using Spark.Core.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
using PNLKey = bts.AccountSecurityKey;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLRowVM : DisposableViewModelBase, IEquatable<PNLRowVM> {
|
||||
public static readonly IComparer<PNLRowVM> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowVM>(r => r.key);
|
||||
static readonly Func<bool, string> _fmt_has_day_trades = hdt => hdt ? "Yes" : "No";
|
||||
//
|
||||
readonly PNLCellIndexBuilder<PNLRowData> _cell_map = new PNLCellIndexBuilder<PNLRowData>();
|
||||
readonly PNLCellEmbeddedAnimationModel<PNLRowData> _animator;
|
||||
readonly ISecurityMetadataStore _sec_meta_store;
|
||||
bool _refresh_normalized_data = true, _is_selected;
|
||||
PNLNormalizedData _normalized_data;
|
||||
api.domain.TickDisplaySettingsImmutable? _quantity_display_backing;
|
||||
|
||||
public PNLRowVM(
|
||||
PNLRowData data,
|
||||
ISecurityMetadataStore sec_meta_store,
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances,
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers,
|
||||
Func<double, string> fmt_fill_price,
|
||||
Func<double, string> fmt_market_price) {
|
||||
//
|
||||
this.data = data;
|
||||
_sec_meta_store = sec_meta_store;
|
||||
_animator = new PNLCellEmbeddedAnimationModel<PNLRowData>(default_appearances, triggers);
|
||||
_animator.on_row(data);
|
||||
pnl_price = _cell_map.add_price_double(ColumnKeys.PNLPrice, d => d.pnl_price);
|
||||
account_cell = _cell_map.add(ColumnKeys.Account, d => d.account);
|
||||
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
|
||||
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
|
||||
expiration_cell = _cell_map.add(
|
||||
ColumnKeys.Expiration,
|
||||
d => d.expiration,
|
||||
e => SecurityFormatter.format_expiration(e));
|
||||
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.symbol);
|
||||
security_cell = _cell_map.add(
|
||||
ColumnKeys.Security,
|
||||
d => d.security,
|
||||
null,
|
||||
EqualityComparer<SecurityImmutable?>.Default,
|
||||
Comparer<SecurityImmutable?>.Default);
|
||||
security_key = _cell_map.add(ColumnKeys.SecurityKey, d => d.key.security_id);
|
||||
cpp = _cell_map.add(ColumnKeys.CPP, d => d.effective_cpp);
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl_double(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.SODNetPos, d => d.display_sod_net_pos, fmt_qty);
|
||||
sod_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.SODLongPos, d => d.display_sod_long_pos, fmt_qty);
|
||||
sod_long_avg_price = _cell_map.add_long_price(ColumnKeys.SODLongAvgPrice, d => d.sod_long_avg_price);
|
||||
sod_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.SODShortPos, d => d.display_sod_short_pos, fmt_qty);
|
||||
sod_short_avg_price = _cell_map.add_short_price(ColumnKeys.SODShortAvgPrice, d => d.sod_short_avg_price);
|
||||
sod_delta = _cell_map.add_pnl_double(ColumnKeys.SODDelta, d => d.sod_delta, fmt_greek);
|
||||
sod_gamma = _cell_map.add_pnl_double(ColumnKeys.SODGamma, d => d.sod_gamma, fmt_greek);
|
||||
sod_vega = _cell_map.add_pnl_double(ColumnKeys.SODVega, d => d.sod_vega, fmt_greek);
|
||||
sod_theta = _cell_map.add_pnl_double(ColumnKeys.SODTheta, d => d.sod_theta, fmt_greek);
|
||||
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl_double(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.DayNetPos, d => d.display_day_net_pos, fmt_qty);
|
||||
day_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.DayLongPos, d => d.display_day_long_pos, fmt_qty);
|
||||
day_long_avg_price = _cell_map.add_long_price(ColumnKeys.DayLongAvgPrice, d => d.day_long_avg_price);
|
||||
day_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.DayShortPos, d => d.display_day_short_pos, fmt_qty);
|
||||
day_short_avg_price = _cell_map.add_short_price(ColumnKeys.DayShortAvgPrice, d => d.day_short_avg_price);
|
||||
day_delta = _cell_map.add_pnl_double(ColumnKeys.DayDelta, d => d.day_delta, fmt_greek);
|
||||
day_gamma = _cell_map.add_pnl_double(ColumnKeys.DayGamma, d => d.day_gamma, fmt_greek);
|
||||
day_vega = _cell_map.add_pnl_double(ColumnKeys.DayVega, d => d.day_vega, fmt_greek);
|
||||
day_theta = _cell_map.add_pnl_double(ColumnKeys.DayTheta, d => d.day_theta, fmt_greek);
|
||||
has_day_trades = _cell_map.add(ColumnKeys.HasDayTrades, d => d.has_day_trades, _fmt_has_day_trades);
|
||||
// working
|
||||
working_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.WorkingNetPos, d => d.display_working_net_pos, fmt_qty);
|
||||
var qty_display = _sec_meta_store.get_qty_display(security);
|
||||
working_long_pos = _cell_map.add_working_long(
|
||||
ColumnKeys.WorkingLongPos,
|
||||
d => d.working.long_data,
|
||||
d => d.bid_price,
|
||||
qty_display);
|
||||
working_long_avg_price = _cell_map.add_long_price(ColumnKeys.WorkingLongAvgPrice, d => d.working_long_avg_price);
|
||||
working_short_pos = _cell_map.add_working_short(
|
||||
ColumnKeys.WorkingShortPos, r => r.working.short_data, r => r.ask_price, qty_display);
|
||||
working_short_avg_price = _cell_map.add_short_price(ColumnKeys.WorkingShortAvgPrice, d => d.working_short_avg_price);
|
||||
// total
|
||||
total_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalPNL, d => d.total_pnl);
|
||||
total_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.TotalNetPos, d => d.display_total_net_pos, fmt_qty);
|
||||
total_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.TotalLongPos, d => d.display_total_long_pos, fmt_qty);
|
||||
total_long_avg_price = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPrice, d => d.total_long_avg_price);
|
||||
total_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.TotalShortPos, d => d.display_total_short_pos, fmt_qty);
|
||||
total_short_avg_price = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPrice, d => d.total_short_avg_price);
|
||||
total_delta = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalDelta, d => d.total_delta, fmt_greek);
|
||||
total_gamma = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalGamma, d => d.total_gamma, fmt_greek);
|
||||
total_vega = _cell_map.add_pnl_double(ColumnKeys.TotalVega, d => d.total_vega, fmt_greek);
|
||||
total_theta = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalTheta, d => d.total_theta, fmt_greek);
|
||||
|
||||
// market
|
||||
bid_price = _cell_map.add_price_double(ColumnKeys.BidPrice, d => d.bid_price);
|
||||
ask_price = _cell_map.add_price_double(ColumnKeys.AskPrice, d => d.ask_price);
|
||||
mid_price = _cell_map.add_price_double(ColumnKeys.MidPrice, d => d.mid_price);
|
||||
last_price = _cell_map.add_price_double(ColumnKeys.LastPrice, d => d.last_price);
|
||||
settlement_price = _cell_map.add_price_double(ColumnKeys.SettlementPrice, d => d.settlement_price);
|
||||
|
||||
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
|
||||
_cell_map.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
|
||||
animations = new_animations.Select(kv => new mbts.ui.spark.CellAnimation(kv.Key.column, kv.Key.type, kv.Value.color.to_brush())).ToList();
|
||||
RaisePropertyChanged(nameof(animations));
|
||||
});
|
||||
|
||||
static string fmt_greek(double v) =>
|
||||
v.is_nan() ? "-" : v.ToString("#,0.00");
|
||||
|
||||
string fmt_qty(double v) =>
|
||||
quantity_display.format(v);
|
||||
}
|
||||
|
||||
// events
|
||||
public event Action<PNLNormalizedData> updated;
|
||||
public event Action<bool> is_selected_changed;
|
||||
|
||||
// data
|
||||
public PNLKey key => data.key;
|
||||
public PNLRowData data { get; private set; }
|
||||
public PNLNormalizedData normalized_data {
|
||||
get {
|
||||
if (_refresh_normalized_data) {
|
||||
_refresh_normalized_data = false;
|
||||
// Only count outrights (spread positions should be normalized into outrights)
|
||||
var sec_type = security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
var (
|
||||
sod_long,
|
||||
sod_short,
|
||||
sod_delta,
|
||||
sod_gamma,
|
||||
sod_vega,
|
||||
sod_theta,
|
||||
day_long,
|
||||
day_short,
|
||||
day_delta,
|
||||
day_gamma,
|
||||
day_vega,
|
||||
day_theta) = sec_type switch {
|
||||
Security.ContractOneofCase.Future or Security.ContractOneofCase.Option => (
|
||||
data.sod.long_pos.round_to_decimal(),
|
||||
data.sod.short_pos.round_to_decimal(),
|
||||
data.sod.delta.round_to_decimal(),
|
||||
data.sod.gamma.round_to_decimal(),
|
||||
data.sod.vega.round_to_decimal(),
|
||||
data.sod.theta.round_to_decimal(),
|
||||
data.day.long_pos.round_to_decimal(),
|
||||
data.day.short_pos.round_to_decimal(),
|
||||
data.day.delta.round_to_decimal(),
|
||||
data.day.gamma.round_to_decimal(),
|
||||
data.day.vega.round_to_decimal(),
|
||||
data.day.theta.round_to_decimal()),
|
||||
_ => (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
};
|
||||
|
||||
var working_long = data.working_long_pos.round_to_decimal();
|
||||
var working_short = data.working_short_pos.round_to_decimal();
|
||||
|
||||
var nd = new PNLNormalizedData() {
|
||||
// sod
|
||||
sod_pnl = sod_pnl.value.round_to_decimal(),
|
||||
sod_long_pos = sod_long,
|
||||
sod_short_pos = sod_short,
|
||||
sod_delta = sod_delta,
|
||||
sod_gamma = sod_gamma,
|
||||
sod_vega = sod_vega,
|
||||
sod_theta = sod_theta,
|
||||
display_sod_long_pos = sod_long,
|
||||
display_sod_short_pos = sod_short,
|
||||
|
||||
// day
|
||||
day_pnl = day_pnl.value.round_to_decimal(),
|
||||
day_long_pos = day_long,
|
||||
day_short_pos = day_short,
|
||||
day_delta = day_delta,
|
||||
day_gamma = day_gamma,
|
||||
day_vega = day_vega,
|
||||
day_theta = day_theta,
|
||||
display_day_long_pos = day_long,
|
||||
display_day_short_pos = day_short,
|
||||
|
||||
// working
|
||||
working_long_pos = working_long,
|
||||
working_short_pos = working_short,
|
||||
display_working_long_pos = working_long,
|
||||
display_working_short_pos = working_short
|
||||
};
|
||||
if (_normalized_data != nd) {
|
||||
_normalized_data = nd;
|
||||
updated?.Invoke(nd);
|
||||
}
|
||||
}
|
||||
return _normalized_data;
|
||||
}
|
||||
}
|
||||
// security
|
||||
public string account_name => data.account_name;
|
||||
public Venue exchange => security?.Exchange ?? Venue.Unset;
|
||||
public string symbol => security.symbol_or_default();
|
||||
public DateTime expiration => data.expiration;
|
||||
public Security.ContractOneofCase security_type => security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
public SecurityImmutable? security => data.security;
|
||||
// cells
|
||||
public PNLCellVM<PNLRowData, AccountTuple> account_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Venue> exchange_cell { get; }
|
||||
public PNLCellVM<PNLRowData, DateTime> expiration_cell { get; }
|
||||
public PNLCellVM<PNLRowData, string> symbol_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Security.ContractOneofCase> security_type_cell { get; }
|
||||
public PNLCellVM<PNLRowData, SecurityImmutable?> security_cell { get; }
|
||||
public PNLCellVM<PNLRowData, uint> security_key { get; }
|
||||
public PNLCellVM<PNLRowData, double> cpp { get; }
|
||||
// sod
|
||||
public PNLCellVM<PNLRowData, double> sod_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_theta { get; }
|
||||
|
||||
// day
|
||||
public PNLCellVM<PNLRowData, double> day_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_theta { get; }
|
||||
public PNLCellVM<PNLRowData, bool> has_day_trades { get; }
|
||||
// working
|
||||
public PNLCellVM<PNLRowData, double> working_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_short_avg_price { get; }
|
||||
// total
|
||||
public PNLCellVM<PNLRowData, double> total_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_theta { get; }
|
||||
|
||||
// (market)
|
||||
public PNLCellVM<PNLRowData, double> bid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> mid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> ask_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> last_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> settlement_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> pnl_price { get; }
|
||||
|
||||
// display
|
||||
public api.domain.TickDisplaySettingsImmutable quantity_display {
|
||||
get {
|
||||
if (_quantity_display_backing.HasValue) {
|
||||
return _quantity_display_backing.Value;
|
||||
}
|
||||
if (security.HasValue) {
|
||||
_quantity_display_backing = _sec_meta_store.get_qty_display(security.Value);
|
||||
}
|
||||
return _quantity_display_backing ??= Security.Defaults.quantity_display_settingsi;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<mbts.ui.spark.CellAnimation> animations { get; private set; }
|
||||
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set {
|
||||
if (Set(ref _is_selected, value))
|
||||
is_selected_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<PNLCellVM<PNLRowData, double>> _market_price_cells {
|
||||
get {
|
||||
yield return pnl_price;
|
||||
yield return last_price;
|
||||
yield return bid_price;
|
||||
yield return mid_price;
|
||||
yield return ask_price;
|
||||
yield return settlement_price;
|
||||
}
|
||||
}
|
||||
|
||||
// includes sod, day, total, and working avg price columns
|
||||
IEnumerable<PNLCellVM<PNLRowData, double>> _fill_price_cells {
|
||||
get {
|
||||
// sod
|
||||
yield return sod_long_avg_price;
|
||||
yield return sod_short_avg_price;
|
||||
// day
|
||||
yield return day_long_avg_price;
|
||||
yield return day_short_avg_price;
|
||||
// total
|
||||
yield return total_long_avg_price;
|
||||
yield return total_short_avg_price;
|
||||
// working
|
||||
yield return working_long_avg_price;
|
||||
yield return working_short_avg_price;
|
||||
}
|
||||
}
|
||||
|
||||
public void update(PNLRowData data) {
|
||||
_quantity_display_backing = null;
|
||||
_refresh_normalized_data = true;
|
||||
this.data = data;
|
||||
_cell_map.on_row(data);
|
||||
_animator.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
}
|
||||
|
||||
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers, Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
|
||||
_animator.on_settings(defaults, triggers);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
|
||||
}
|
||||
|
||||
public bool Equals(PNLRowVM other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
$"account: {account_name} exchange: {exchange} symbol: {symbol} security: {security} buy_quantity: {day_long_pos} sell_quantity: {day_short_pos} pnl: {total_pnl}";
|
||||
|
||||
void _set_price_formatting_delegates(Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
|
||||
foreach (var cell in _fill_price_cells) {
|
||||
cell.format = fmt_fill_price;
|
||||
}
|
||||
foreach (var cell in _market_price_cells) {
|
||||
cell.format = fmt_market_price;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,9 +65,9 @@ namespace BCG {
|
||||
public static NMSheetTheoBBOCross get_cross_state(double bid_price, double ask_price, double theo, double delta, bool is_call, double driver_tick_size, bool is_proximity_alert_enabled) {
|
||||
double abs_delta = Math.Abs(delta);
|
||||
abs_delta = abs_delta == 0 ? Double.Epsilon : abs_delta;
|
||||
if (!Double.IsNaN(theo) && !Double.IsNaN(bid_price) && (bid_price - theo) > _basic_crosss_eps)
|
||||
if (!Double.IsNaN(theo) && !Double.IsNaN(bid_price) && theo < bid_price)
|
||||
return new NMSheetTheoBBOCross(CrossState.bid_cross, 0);
|
||||
else if (!Double.IsNaN(theo) && !Double.IsNaN(ask_price) && (theo - ask_price) > _basic_crosss_eps)
|
||||
else if (!Double.IsNaN(theo) && !Double.IsNaN(ask_price) && theo > ask_price)
|
||||
return new NMSheetTheoBBOCross(CrossState.ask_cross, 0);
|
||||
else if (is_proximity_alert_enabled) {
|
||||
/// bool is passed to bypass doing the below
|
||||
@@ -87,7 +87,6 @@ namespace BCG {
|
||||
return NMSheetTheoBBOCross.get_nulled_obj();
|
||||
}
|
||||
|
||||
private static double _basic_crosss_eps = 0.01;
|
||||
private static double _n_max_ticks = 100.0;
|
||||
private static NMSheetTheoBBOCross _nulled_obj = new NMSheetTheoBBOCross(CrossState.none, 0);
|
||||
public static NMSheetTheoBBOCross get_nulled_obj() { return _nulled_obj; }
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
|
||||
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
|
||||
<cv:NullableDoubleConverter x:Key="TickDefaultPriceConverter" DefaultValue="0"/>
|
||||
<Style TargetType="Button" BasedOn="{StaticResource DefaultButton}">
|
||||
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Height" Value="{StaticResource ButtonHeight}"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
@@ -53,6 +53,8 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="140"/>
|
||||
@@ -75,7 +77,6 @@
|
||||
RegionalTick="{Binding price_tick}"
|
||||
Focusable="false"
|
||||
TickDefaultValue="{Binding current_price, Converter={StaticResource TickDefaultPriceConverter}, FallbackValue={u:Double 0}}"
|
||||
ToolTip="Price"
|
||||
Value="{Binding price}"
|
||||
/>
|
||||
<c:TickUpDownNullableDouble
|
||||
@@ -87,7 +88,6 @@
|
||||
Increment="{Binding size_calculator.unit_quantity}"
|
||||
Minimum="0"
|
||||
TickDisplaySettings="{Binding size_calculator.display_settings}"
|
||||
ToolTip="Size"
|
||||
Value="{Binding size_calculator.display_value}"
|
||||
/>
|
||||
</Grid>
|
||||
@@ -97,24 +97,13 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
Command="{Binding set_price_command}"
|
||||
CommandParameter="{Binding bbo.bid}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
FontWeight="Bold"
|
||||
ToolTip="Bid Price">
|
||||
<Button Command="{Binding set_price_command}" CommandParameter="{Binding bbo.bid}" Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="bbo.bid"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
Command="{Binding set_price_command}"
|
||||
CommandParameter="{Binding bbo.ask}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
FontWeight="Bold"
|
||||
ToolTip="Ask Price">
|
||||
<Button Grid.Column="2" Command="{Binding set_price_command}" CommandParameter="{Binding bbo.ask}" Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="bbo.ask"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
@@ -131,7 +120,7 @@
|
||||
Command="{Binding size_calculator.set_actual_value_command}"
|
||||
CommandParameter="{Binding bbo.bid_size}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
ToolTip="Bid Size">
|
||||
>
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
|
||||
<Binding Path="bbo.bid_size" />
|
||||
<Binding Path="qty_display" />
|
||||
@@ -142,20 +131,20 @@
|
||||
Command="{Binding size_calculator.set_actual_value_command}"
|
||||
CommandParameter="{Binding bbo.ask_size}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
ToolTip="Ask Size">
|
||||
>
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
|
||||
<Binding Path="bbo.ask_size" />
|
||||
<Binding Path="qty_display" />
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="4">
|
||||
<Grid Grid.Row="4" Grid.RowSpan="3">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<ItemsControl Margin="0,0,-4,-4" DataContext="{Binding size_calculator}" ItemsSource="{Binding buttons}" Focusable="False">
|
||||
<ItemsControl Margin="-2,-2,-2,0" DataContext="{Binding size_calculator}" ItemsSource="{Binding buttons}" Focusable="False">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="3" Focusable="False"/>
|
||||
@@ -164,11 +153,11 @@
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button
|
||||
Margin="0,0,4,4"
|
||||
Margin="2,4,2,0"
|
||||
Content="{Binding}"
|
||||
Focusable="False"
|
||||
Command="{Binding Path=DataContext.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
ToolTip="Add to Size">
|
||||
>
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
|
||||
<Binding />
|
||||
@@ -181,35 +170,35 @@
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Command="{Binding size_calculator.clear}"
|
||||
Content="Clear"
|
||||
ToolTip="Clear Size" />
|
||||
<Button Grid.Row="2" Command="{Binding size_calculator.clear}">Clear</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="4" Grid.Column="2">
|
||||
<Grid Grid.Row="4" Grid.Column="2" Grid.RowSpan="3">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<ComboBox
|
||||
Grid.ColumnSpan="2"
|
||||
usp:OrderModificationControl.IsDirty="{Binding account_dirty, FallbackValue={u:False}}"
|
||||
IsEnabled="{Binding can_change_account, FallbackValue={u:True}}"
|
||||
ItemsSource="{Binding properties_vm.accounts}"
|
||||
Margin="0,2"
|
||||
SelectedItem="{Binding properties_vm.selected_account}"
|
||||
ToolTip="Account" />
|
||||
/>
|
||||
<ComboBox
|
||||
Grid.Row="2"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
usp:OrderModificationControl.IsDirty="{Binding time_in_force_dirty, FallbackValue={u:False}}"
|
||||
IsEnabled="{Binding can_change_time_in_force, FallbackValue={u:True}}"
|
||||
ItemsSource="{Binding properties_vm.time_in_forces}"
|
||||
Margin="0,2"
|
||||
SelectedItem="{Binding properties_vm.selected_time_in_force}"
|
||||
ToolTip="Time In Force" />
|
||||
/>
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="4"
|
||||
Grid.Row="2"
|
||||
Margin="0,2"
|
||||
DataContext="{Binding properties_vm}"
|
||||
usp:OrderModificationControl.IsDirty="{Binding DataContext.order_type_dirty, RelativeSource={RelativeSource AncestorType=usp:OrderModificationControl}, FallbackValue={u:False}}"
|
||||
HasEditableProperties="{Binding can_expand, FallbackValue={u:False}}"
|
||||
@@ -218,7 +207,7 @@
|
||||
IsOpen="{Binding is_expanded, FallbackValue={u:False}}"
|
||||
ItemsSource="{Binding algo_definitions_view}"
|
||||
SelectedItem="{Binding selected_algo_definition}"
|
||||
ToolTip="Order/Algo Type">
|
||||
>
|
||||
<uag:OrderDetailsPopupControl.Style>
|
||||
<Style TargetType="uag:OrderDetailsPopupControl" BasedOn="{StaticResource {x:Type uag:OrderDetailsPopupControl}}">
|
||||
<Style.Triggers>
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace mbts.spark {
|
||||
new PropertyValidation<AccountTuple?>(
|
||||
a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account"))
|
||||
});
|
||||
attach(_accounts, accounts_src.Select(accs => accs.Values.ToListSorted()), inline_account_src);
|
||||
attach(_accounts, accounts_src.Select(accs => accs.Values.ToList()), inline_account_src);
|
||||
|
||||
// time in force
|
||||
_time_in_force = new SelectorProperty<TimeInForce>(
|
||||
|
||||
@@ -1,161 +1,161 @@
|
||||
<Window
|
||||
x:Class="mbts.ui.spark.OrderTicket"
|
||||
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=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 bbo, Mode=OneWay}"
|
||||
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>
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<usp:OrderModificationControl Grid.ColumnSpan="3" />
|
||||
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}" />
|
||||
<Button Grid.Row="2" Grid.Column="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}" />
|
||||
</Grid>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Style="{StaticResource TextBlock.Dark}">
|
||||
<TextBlock.Inlines>
|
||||
<Run FontWeight="Bold" Text="{Binding security, Mode=OneWay}" />
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10">
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
|
||||
<Binding Path="bid_size" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
|
||||
</MultiBinding>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="bid"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="ask"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10">
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
|
||||
<Binding Path="ask_size" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
|
||||
</MultiBinding>
|
||||
</Run>
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
</Window>
|
||||
<Window
|
||||
x:Class="mbts.ui.spark.OrderTicket"
|
||||
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=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 bbo, Mode=OneWay}"
|
||||
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}" />
|
||||
<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}" />
|
||||
<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>
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<usp:OrderModificationControl Grid.ColumnSpan="3" />
|
||||
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}" />
|
||||
<Button Grid.Row="2" Grid.Column="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}" />
|
||||
</Grid>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Style="{StaticResource TextBlock.Dark}">
|
||||
<TextBlock.Inlines>
|
||||
<Run FontWeight="Bold" Text="{Binding security, Mode=OneWay}" />
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10">
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
|
||||
<Binding Path="bid_size" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
|
||||
</MultiBinding>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="bid"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="ask"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10">
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
|
||||
<Binding Path="ask_size" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
|
||||
</MultiBinding>
|
||||
</Run>
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
</Window>
|
||||
@@ -64,7 +64,8 @@ namespace mbts.analytics {
|
||||
if (tl.tl_row.size > 0) {
|
||||
start_long_pos += tl.tl_row.size;
|
||||
start_long_total_cost += cost;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
start_short_pos += Math.Abs(tl.tl_row.size);
|
||||
start_short_total_cost += cost;
|
||||
}
|
||||
@@ -77,7 +78,8 @@ namespace mbts.analytics {
|
||||
if (tl.tl_row.size > 0) {
|
||||
day_long_pos += tl.tl_row.size;
|
||||
day_long_total_cost += cost;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
day_short_pos += Math.Abs(tl.tl_row.size);
|
||||
day_short_total_cost += cost;
|
||||
}
|
||||
@@ -163,13 +165,6 @@ namespace mbts.analytics {
|
||||
is_day_trade = _is_day_trade(tl)
|
||||
};
|
||||
pnl_trades.Add(pnl_tl);
|
||||
} else if (tl.security_type == SecurityType.Spot) {
|
||||
PNLTradeRow pnl_tl = new PNLTradeRow() {
|
||||
tl_row = tl,
|
||||
md = tl_md,
|
||||
is_day_trade = _is_day_trade(tl)
|
||||
};
|
||||
pnl_trades.Add(pnl_tl);
|
||||
}
|
||||
}
|
||||
return pnl_trades;
|
||||
|
||||
@@ -4,9 +4,6 @@ using bts.api;
|
||||
|
||||
using BCG;
|
||||
using mbts.co2;
|
||||
using System.Xml.Serialization;
|
||||
using ZstdSharp.Unsafe;
|
||||
using bts.proto;
|
||||
|
||||
namespace mbts.analytics {
|
||||
// Pairs ITradeLogRow along with the data (theo, md) that is needed to compute
|
||||
@@ -21,88 +18,20 @@ namespace mbts.analytics {
|
||||
|
||||
public SecurityImmutable security { get { return md.security; } }
|
||||
|
||||
// private double _option_pnl(double trade_price, double now_price) {
|
||||
// double coin_terms = (now_price - trade_price) * tl_row.size;
|
||||
// double usd_terms = coin_terms * driver;
|
||||
// return usd_terms;
|
||||
// }
|
||||
|
||||
private bool _is_notional {
|
||||
get {
|
||||
return false;
|
||||
}
|
||||
private double _option_pnl(double trade_price, double now_price) {
|
||||
double coin_terms = (now_price - trade_price) * tl_row.size;
|
||||
double usd_terms = coin_terms * driver;
|
||||
return usd_terms;
|
||||
}
|
||||
|
||||
private bool _is_inverse_option_contract {
|
||||
get {
|
||||
return false;
|
||||
}
|
||||
private double _future_pnl(double trade_price, double now_price) {
|
||||
// the qty is in USD, so this is the initial cash flow
|
||||
double init_asset_value = tl_row.size;
|
||||
double asset_size = init_asset_value / tl_row.price;
|
||||
double now_asset_value = asset_size * now_price;
|
||||
return now_asset_value - init_asset_value;
|
||||
}
|
||||
|
||||
private double _cpp {
|
||||
get {
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _is_reverse_option_contract {
|
||||
get {
|
||||
// check for Deribit options
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private double _option_pnl {
|
||||
get {
|
||||
if (_is_notional) {
|
||||
var now_price = theo;
|
||||
double coin_terms = (now_price - tl_row.price) * tl_row.size;
|
||||
double usd_terms = coin_terms * driver;
|
||||
return usd_terms;
|
||||
} else {
|
||||
return _cpp * tl_row.size * (theo - tl_row.price);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double _spot_pnl {
|
||||
get {
|
||||
// both are computed the same way
|
||||
return _future_pnl;
|
||||
}
|
||||
}
|
||||
|
||||
private double _future_pnl {
|
||||
get {
|
||||
if (_is_notional) {
|
||||
// TODO:
|
||||
// let it fall through right now
|
||||
// the qty is in USD, so this is the initial cash flow
|
||||
var now_price = theo;
|
||||
double init_asset_value = tl_row.size;
|
||||
double asset_size = init_asset_value / tl_row.price;
|
||||
double now_asset_value = asset_size * now_price;
|
||||
return now_asset_value - init_asset_value;
|
||||
} else {
|
||||
// TODO: is position pnl, compute it wrt settlement price?
|
||||
return tl_row.size * (theo - tl_row.price);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private double _future_pnl(double trade_price, double now_price) {
|
||||
// if (_is_notional) {
|
||||
// // the qty is in USD, so this is the initial cash flow
|
||||
// double init_asset_value = tl_row.size;
|
||||
// double asset_size = init_asset_value / tl_row.price;
|
||||
// double now_asset_value = asset_size * now_price;
|
||||
// return now_asset_value - init_asset_value;
|
||||
// } else {
|
||||
// return tl_row.size * (theo - tl_row.price);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
public double driver {
|
||||
get {
|
||||
if (tl_row.security_type == SecurityType.Option) {
|
||||
@@ -115,46 +44,35 @@ namespace mbts.analytics {
|
||||
}
|
||||
public double theo {
|
||||
get {
|
||||
switch (tl_row.security_type) {
|
||||
// TODO:
|
||||
case SecurityType.Option:
|
||||
if (_is_inverse_option_contract) {
|
||||
return tl_row.call_put == PutOrCall.Call ?
|
||||
opt_theo.fwd_normalized_call : opt_theo.fwd_normalized_put;
|
||||
} else {
|
||||
return tl_row.call_put == PutOrCall.Call ?
|
||||
opt_theo.call : opt_theo.put;
|
||||
}
|
||||
// future and options, just get mid market
|
||||
case SecurityType.Future:
|
||||
//return fut_theo.theo;
|
||||
case SecurityType.Spot:
|
||||
var bbo = top_of_book;
|
||||
return (bbo.Item1 + bbo.Item2) / 2;
|
||||
default:
|
||||
return Double.NaN;
|
||||
if (tl_row.security_type == SecurityType.Option) {
|
||||
return tl_row.call_put == PutOrCall.Call ?
|
||||
opt_theo.fwd_normalized_call : opt_theo.fwd_normalized_put;
|
||||
} else if (tl_row.security_type == SecurityType.Future) {
|
||||
return fut_theo.theo;
|
||||
}
|
||||
return Double.NaN;
|
||||
}
|
||||
}
|
||||
public double pnl {
|
||||
get {
|
||||
double rv = Double.NaN;
|
||||
switch (tl_row.security_type) {
|
||||
case SecurityType.Option:
|
||||
rv = _option_pnl;
|
||||
break;
|
||||
case SecurityType.Future:
|
||||
rv = _future_pnl;
|
||||
break;
|
||||
case SecurityType.Spot:
|
||||
// = tl_row.size * (theo - tl_row.price);
|
||||
rv = _spot_pnl;
|
||||
break;
|
||||
if (is_day_trade) {
|
||||
if (tl_row.security_type == SecurityType.Future) {
|
||||
rv = _future_pnl(tl_row.price, theo);
|
||||
} else if (tl_row.security_type == SecurityType.Option) {
|
||||
rv = _option_pnl(tl_row.price, theo);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
// use settle price ?
|
||||
if (tl_row.security_type == SecurityType.Future) {
|
||||
rv = _future_pnl(tl_row.price, theo);
|
||||
} else if (tl_row.security_type == SecurityType.Option) {
|
||||
rv = _option_pnl(tl_row.price, theo);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
public double settle_price {
|
||||
get {
|
||||
return md.md == null ? Double.NaN : md.md.settlement_price;
|
||||
|
||||
@@ -36,10 +36,6 @@ namespace mbts {
|
||||
}
|
||||
|
||||
public static bool is_unexpired(bts.api.SecurityTuple st, DateTime exp_date) {
|
||||
if (st.Venue == Venue.Cboe || st.Venue == Venue.Glbx) {
|
||||
// server is already getting teh righ contracts
|
||||
return true;
|
||||
}
|
||||
TimeSpan exp_time = get_exp_time(st);
|
||||
DateTime full_exp_utc = exp_date.ToUniversalTime().Date.Add(exp_time);
|
||||
DateTime now_utc = DateTime.UtcNow;
|
||||
@@ -47,8 +43,6 @@ namespace mbts {
|
||||
}
|
||||
|
||||
public static bool is_unexpired(bts.api.SecurityImmutable sec) {
|
||||
if (sec.security_tuple().Venue == Venue.Cboe || sec.security_tuple().Venue == Venue.Glbx)
|
||||
return true;
|
||||
var sec_type = sec.security_type();
|
||||
switch (sec_type) {
|
||||
case bts.api.SecurityType.Future:
|
||||
|
||||
@@ -58,10 +58,9 @@ namespace BCG {
|
||||
}
|
||||
// TODO: put it as a setting
|
||||
bts.Log.assert(theos.Length == drivers.Length);
|
||||
// only do this when its deribit crypto option, TODO
|
||||
// for (int i = 0; i < theos.Length; i++) {
|
||||
// theos[i] /= drivers[i];
|
||||
// }
|
||||
for (int i = 0; i < theos.Length; i++) {
|
||||
theos[i] /= drivers[i];
|
||||
}
|
||||
return new SpreadValue(spread_spec, theos, deltas, gammas, vegas, adj_vegas, oevs, thetas, slope_vegas, drivers, qs_midpoint_theos, pos, additional_delta);
|
||||
}
|
||||
}
|
||||
@@ -143,21 +142,9 @@ namespace BCG {
|
||||
return tp ?? mbts.spark.TheoPoint.getNulledObject();
|
||||
}
|
||||
|
||||
private static mbts.spark.TheoPoint _compute_single_option_leg_at_tied_price(DateTime option_expiration, double strike, bool is_call, OptionTheoInterpolationData sparse_theo_map,
|
||||
ExpStrikeKeyedData<mbts.spark.TheoPoint> live_symbol_theo_map, double driver_price) {
|
||||
// mbts.spark.TheoPoint tp = sparse_theo_map == null ? mbts.spark.TheoPoint.getNulledObject() : sparse_theo_map.interpolate_theos(option_expiration, strike, driver_price);
|
||||
// return tp ?? mbts.spark.TheoPoint.getNulledObject();
|
||||
// TOD: config here
|
||||
return _compute_single_option_leg_at_tied_price_from_live(option_expiration, strike, is_call, live_symbol_theo_map, driver_price);
|
||||
}
|
||||
|
||||
private static mbts.spark.TheoPoint _compute_single_option_leg_at_tied_price_from_live(DateTime option_expiration, double strike, bool is_call,
|
||||
ExpStrikeKeyedData<mbts.spark.TheoPoint> live_symbol_theo_map, double driver_price) {
|
||||
var tp = live_symbol_theo_map.get_data(option_expiration, strike);
|
||||
if (tp == null)
|
||||
return mbts.spark.TheoPoint.getNulledObject();
|
||||
return TheoPoint.extrapolate(tp, driver_price, is_call);
|
||||
|
||||
private static mbts.spark.TheoPoint _compute_single_option_leg_at_tied_price(DateTime option_expiration, double strike, bool is_call, OptionTheoInterpolationData sparse_theo_map, double driver_price) {
|
||||
mbts.spark.TheoPoint tp = sparse_theo_map == null ? mbts.spark.TheoPoint.getNulledObject() : sparse_theo_map.interpolate_theos(option_expiration, strike, driver_price);
|
||||
return tp ?? mbts.spark.TheoPoint.getNulledObject();
|
||||
}
|
||||
|
||||
/// this is the only relatively tricky thing here
|
||||
@@ -187,7 +174,7 @@ namespace BCG {
|
||||
if (leg_price_spec.primaryFrozen) /// tied
|
||||
{
|
||||
effective_driver = leg_price_spec.primary;
|
||||
return _compute_single_option_leg_at_tied_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, symbol_sparse_theo_map, live_symbol_theo_map, leg_price_spec.primary);
|
||||
return _compute_single_option_leg_at_tied_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, symbol_sparse_theo_map, leg_price_spec.primary);
|
||||
} else {
|
||||
mbts.spark.TheoPoint tp = _compute_single_option_leg_at_live_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, live_symbol_theo_map);
|
||||
effective_driver = tp.driver;
|
||||
@@ -201,7 +188,7 @@ namespace BCG {
|
||||
future_theo_map.TryGetValue(driver_price_info.first, out symbol_future_theo_map);
|
||||
double driver_price = StatelessFutureTheoCalculator.transform_price(symbol_future_theo_map, leg_price_spec.driverExpiration, leg_price_spec.primary, driver_price_info.second);
|
||||
effective_driver = leg_price_spec.primary;
|
||||
return _compute_single_option_leg_at_tied_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, symbol_sparse_theo_map, live_symbol_theo_map, driver_price);
|
||||
return _compute_single_option_leg_at_tied_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, symbol_sparse_theo_map, driver_price);
|
||||
} else {
|
||||
bts.Log.Ui.error(String.Format("This spread is live but the driver expiration {0} is different from the system driver expiration {1} for {2}",
|
||||
spread_option_leg.legPriceSpec.driverExpiration, driver_price_info.second, spread_option_leg.contract.product));
|
||||
|
||||
@@ -216,10 +216,8 @@ namespace mbts.ui {
|
||||
public static BTSColor ask_text => BTSCoreColors.red_dark;
|
||||
public static BTSColor bid_text => BTSCoreColors.blue;
|
||||
public static BTSColor bid => 0xff76b7fa;
|
||||
public static BTSColor bid_dark => 0xff26239a;
|
||||
public static BTSColor bid_alt => BTSCoreColors.blue_light.with(0x70);
|
||||
public static BTSColor ask => 0xffff6666;
|
||||
public static BTSColor ask_dark => 0xff7a2331;
|
||||
public static BTSColor ask_alt => 0x70ffb4b4;
|
||||
public static BTSColor last => BTSCoreColors.black;
|
||||
public static BTSColor bid_ask_alt_blend => BTSCoreColors.purple_light;
|
||||
@@ -229,7 +227,7 @@ namespace mbts.ui {
|
||||
|
||||
public static BTSColor atm_background => BTSCoreColors.gray_dark;
|
||||
|
||||
public static BTSColor session_low => 0xff8f5924;
|
||||
public static BTSColor session_low => 0xff59596b;
|
||||
public static BTSColor session_high => 0xff239b00;
|
||||
public static BTSColor session_range_fill => 0x1a000000;
|
||||
|
||||
@@ -986,7 +984,7 @@ namespace mbts.ui {
|
||||
public static BTSColor alternate_column_foreground => UI.foreground_standout;
|
||||
public static BTSColor bid_line => BTSCoreColors.blue_light_dark_saturated;
|
||||
public static BTSColor ask_line => BTSCoreColors.red_light_dark_saturated;
|
||||
public static BTSColor mid_line => 0xffa4a4a4;
|
||||
public static BTSColor mid_line => 0x40000000;
|
||||
|
||||
public static BTSColor default_column_background => 0xff141420;//0xff28283b;
|
||||
public static BTSColor alternate_column_background => 0xff141420;// 0xff494957;
|
||||
|
||||
@@ -5,8 +5,7 @@ using mbts.ui.utility.text;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Core.Protobuf;
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
@@ -14,7 +13,7 @@ using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.proto {
|
||||
[ProtoContract]
|
||||
class FontParamsProto : IProtobuf<FontParamsProto> {
|
||||
class FontParamsProto : IEquatable<FontParamsProto> {
|
||||
[ProtoMember(1)]
|
||||
public TypefaceProto typeface { get; set; }
|
||||
[ProtoMember(2)]
|
||||
@@ -25,27 +24,6 @@ namespace bts.proto {
|
||||
public string font_family => typeface.font_family;
|
||||
public int font_weight => typeface.font_weight;
|
||||
|
||||
public FontParamsProto With(TypefaceProto? typeface = null, double? font_size = null, uint? font_color = null, bool in_place = false) {
|
||||
var rv = in_place ? this : Clone();
|
||||
if (typeface.HasValue) {
|
||||
rv.typeface = typeface.Value;
|
||||
}
|
||||
if (font_size.HasValue) {
|
||||
rv.font_size = font_size.Value;
|
||||
}
|
||||
if (font_color.HasValue) {
|
||||
rv.font_color = font_color.Value;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public FontParamsProto Clone() =>
|
||||
new() {
|
||||
typeface = typeface.Clone(),
|
||||
font_size = font_size,
|
||||
font_color = font_color,
|
||||
};
|
||||
|
||||
public bool Equals(FontParamsProto other) =>
|
||||
other != null &&
|
||||
typeface == other.typeface &&
|
||||
|
||||
@@ -4,15 +4,14 @@ using mbts.ui.utility.text;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Core.Protobuf;
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.proto {
|
||||
[ProtoContract]
|
||||
readonly struct TypefaceProto : IProtobuf<TypefaceProto> {
|
||||
readonly struct TypefaceProto : IEquatable<TypefaceProto> {
|
||||
static readonly int _type_hash = typeof(TypefaceProto).GetHashCode();
|
||||
|
||||
[ProtoMember(1)]
|
||||
@@ -31,9 +30,6 @@ namespace bts.proto {
|
||||
font_family ?? this.font_family,
|
||||
font_weight ?? this.font_weight);
|
||||
|
||||
public TypefaceProto Clone() =>
|
||||
new(font_family.ToString(), font_weight);
|
||||
|
||||
public bool Equals(TypefaceProto other) =>
|
||||
font_family == other.font_family &&
|
||||
font_weight == other.font_weight;
|
||||
|
||||
@@ -379,15 +379,8 @@ namespace mbts.ui.controls {
|
||||
|
||||
class BadgeTextLinesConverter : IMultiValueConverter {
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (ConvertUtils.TryConvertArgs(
|
||||
values,
|
||||
out Side side,
|
||||
out uint quantity,
|
||||
out uint filled,
|
||||
out double price,
|
||||
out LadderSettings.BadgeStyle style)) {
|
||||
return BadgeRender.format(side, quantity, filled, price, style);
|
||||
}
|
||||
if (ConvertUtils.try_convert_args(values, out Side side, out uint quantity, out uint filled, out mbts.spark.LadderSettings.BadgeStyle style))
|
||||
return BadgeRender.format(side, quantity, filled, style);
|
||||
else return new string[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace mbts.ui.controls {
|
||||
namespace mbts.ui.controls {
|
||||
using bts;
|
||||
using bts.utils;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace mbts.ui.controls {
|
||||
public const double
|
||||
Account = 62,
|
||||
CheckMark = 42,
|
||||
Currency = 68,
|
||||
Currency = 54,
|
||||
Exchange = 66,
|
||||
FillKey = 68,
|
||||
OrderKey = 78,
|
||||
@@ -43,9 +43,7 @@ namespace mbts.ui.controls {
|
||||
Side = 47,
|
||||
Qty = 48,
|
||||
MergedQty = 68,
|
||||
NotionalValue = 68,
|
||||
Filled = 56,
|
||||
FilledNotionalValue = 81,
|
||||
Symbol = 60,
|
||||
Time = 112,
|
||||
TimeOfDay = 84,
|
||||
@@ -124,7 +122,6 @@ namespace mbts.ui.controls {
|
||||
SecurityType = "Security Type",
|
||||
Side = "B/S",
|
||||
Qty = "Qty",
|
||||
NotionalValue = "Notional",
|
||||
MergedQty = "Qty",
|
||||
DirectQty = "Qty (D)",
|
||||
ImpliedQty = "Qty (I)",
|
||||
@@ -147,7 +144,6 @@ namespace mbts.ui.controls {
|
||||
FillKind = "Fill Kind",
|
||||
FilledPrice = "Fill Price",
|
||||
FilledQuantity = "Fill Qty",
|
||||
FilledNotionalValue = "Fill Notional",
|
||||
FillStatus = "Fill Status",
|
||||
Name = "Name",
|
||||
Description = "Description",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -12,10 +12,7 @@ namespace mbts.spark.filters {
|
||||
str = str.Trim();
|
||||
if (str == string.Empty)
|
||||
return null;
|
||||
try {
|
||||
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
} catch (RegexParseException) { }
|
||||
return null;
|
||||
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
}
|
||||
}
|
||||
class RegexFilterVM<TRow> : FilterVMBase<TRow, string, string> {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
@@ -22,7 +21,7 @@
|
||||
<GroupBox Header="Font Family" Height="{Binding ActualHeight, ElementName=_panel_right}">
|
||||
<ListBox
|
||||
x:Name="_font_family_list_box"
|
||||
Background="{u:BTSBrush UI.ListBox.normal_background}"
|
||||
Background="White"
|
||||
ItemsSource="{Binding FontFamilies, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
SelectedItem="{Binding SelectedFontFamily, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
>
|
||||
@@ -41,7 +40,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
<GroupBox Header="Font Weight">
|
||||
<ListBox
|
||||
Background="{u:BTSBrush UI.ListBox.normal_background}"
|
||||
Background="White"
|
||||
ItemsSource="{Binding FontWeights, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
SelectedItem="{Binding SelectedFontWeight, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
>
|
||||
@@ -74,7 +73,7 @@
|
||||
FontFamily="{Binding SelectedFontFamily, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
FontSize="{Binding SelectedFontSize, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
FontWeight="{Binding SelectedFontWeight, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
Foreground="{u:BTSBrush UI.default_text}"
|
||||
Foreground="{Binding SelectedFontColor, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}, Converter={StaticResource ColorToBrushConverter}}"
|
||||
TextWrapping="Wrap"
|
||||
>
|
||||
<TextBlock.Inlines>
|
||||
|
||||
@@ -12,12 +12,8 @@
|
||||
x:Name="order_type"
|
||||
Text="{Binding algo_root.order_type, Converter={StaticResource OrderTypeDisplayTextConverter}, StringFormat='{}{0}: ', Mode=OneWay}"
|
||||
FontWeight="Bold"
|
||||
Foreground="{u:BTSBrush UI.default_text}" />
|
||||
<TextBlock
|
||||
x:Name="display_name"
|
||||
Text="{Binding name, Mode=OneWay}"
|
||||
FontWeight="Normal"
|
||||
Foreground="{u:BTSBrush UI.default_text}" />
|
||||
/>
|
||||
<TextBlock x:Name="display_name" Text="{Binding name, Mode=OneWay}" FontWeight="Normal"/>
|
||||
</StackPanel>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Value="{u:True}">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<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:BTSBrush MarketData.ask_dark}"/>
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}"/>
|
||||
<Setter Property="BorderBrush" Value="{u:BTSColor UI.Button.normal_border}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}" />
|
||||
<Setter Property="MinHeight" Value="40"/>
|
||||
@@ -32,7 +32,7 @@
|
||||
</Style>
|
||||
|
||||
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="{u:BTSBrush MarketData.bid_dark}"/>
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.bid}"/>
|
||||
<Setter Property="BorderBrush" Value="{u:BTSColor UI.Button.normal_border}"/>
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}" />
|
||||
|
||||
@@ -87,10 +87,10 @@
|
||||
</c:TickUpDown>
|
||||
</ControlTemplate>
|
||||
<Style TargetType="usp:PriceEntryControl">
|
||||
<Setter Property="Background" Value="{u:BTSBrush UI.Button.normal_background}" />
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.input_background}" />
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.input_text}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Template" Value="{StaticResource PriceEntryTemplate}"/>
|
||||
</Style>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<Style TargetType="{x:Type c:TickUpDown}">
|
||||
<Setter Property="Background" Value="{StaticResource TextBox.Static.Background.Brush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Height" Value="20" />
|
||||
@@ -75,8 +75,7 @@
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{u:BTSBrush UI.Button.mouseover_background}"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{u:BTSBrush UI.Button.mouseover_border}"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource TextBox.MouseOver.Border.Brush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
@@ -134,8 +133,7 @@
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{u:BTSBrush UI.Button.mouseover_background}"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{u:BTSBrush UI.Button.mouseover_border}"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource TextBox.MouseOver.Border.Brush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
@@ -147,7 +145,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -190,7 +188,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -232,7 +230,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -274,7 +272,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -312,7 +310,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -350,7 +348,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -386,7 +384,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -430,7 +428,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -468,7 +466,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -550,7 +548,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -588,7 +586,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
|
||||
@@ -557,8 +557,6 @@
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DefaultButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}" />
|
||||
|
||||
<Style TargetType="RepeatButton">
|
||||
<Setter Property="Foreground" Value="{u:BTSBrush UI.Button.normal_foreground}" />
|
||||
<Setter Property="Background" Value="{u:BTSBrush UI.Button.normal_background}" />
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
}
|
||||
|
||||
void PerformMEFComposition(string config_source) {
|
||||
Log.Ui.info($"Generating MEF dependencies for the shell" +
|
||||
Log.Ui.info($"Generating MEF dependencies for the shell " +
|
||||
$"({typeof(IInitializable)}s, {typeof(IShellModule)}s, " +
|
||||
$"{typeof(IShellDaemon)}s, etc.)");
|
||||
try {
|
||||
|
||||
@@ -47,16 +47,16 @@ namespace mbts.spark {
|
||||
readonly SecurityImmutable _security;
|
||||
readonly MBOSecurityQueryImmutable _query;
|
||||
readonly Dictionary<(string, bool), FormattedText> _cache;
|
||||
readonly Func<double, double, string> _fmt_mbo_qty;
|
||||
readonly Func<int, string> _fmt_mbo_qty;
|
||||
int _max_order_size = 1;
|
||||
double _height = 0;
|
||||
double _number_width = 0;
|
||||
|
||||
public MBOPopupModel(MBOService mbo, SecurityImmutable security, MBOSecurityQueryImmutable query, Func<double, double, string> fmt_mbo_qty) {
|
||||
public MBOPopupModel(MBOService mbo, SecurityImmutable security, MBOSecurityQueryImmutable query, Func<int, string> fmt_mbo_qty) {
|
||||
_mbo = mbo;
|
||||
_security = security;
|
||||
_query = query;
|
||||
_fmt_mbo_qty = fmt_mbo_qty ?? new Func<double, double, string>((v, p) => v.ToString());
|
||||
_fmt_mbo_qty = fmt_mbo_qty ?? new Func<int, string>(v => v.ToString());
|
||||
|
||||
// prime the cache with numbers on the left hand side
|
||||
_cache =
|
||||
@@ -112,7 +112,7 @@ namespace mbts.spark {
|
||||
|
||||
pdp.row_numbers = p.orders.Select((o, i) => format_text((i + 1).ToString(), ismine(o))).ToList();
|
||||
pdp.numbers_width = _number_width = Math.Max(_number_width, (int)pdp.row_numbers.Max(r => r.Width) + text_margin);
|
||||
pdp.row_labels = p.orders.Select(o => format_text(_fmt_mbo_qty(o.Size, o.Price) + (ismine(o) ? "*" : ""), ismine(o))).ToList();
|
||||
pdp.row_labels = p.orders.Select(o => format_text(_fmt_mbo_qty(o.Size) + (ismine(o) ? "*" : ""), ismine(o))).ToList();
|
||||
var max_label_width = pdp.row_labels.Max(l => l.Width) + text_margin;
|
||||
|
||||
var available_width = pdp.width - pdp.numbers_width - max_label_width;
|
||||
|
||||
@@ -1,401 +1,335 @@
|
||||
using bts.api;
|
||||
using bts.api.domain;
|
||||
using bts.utils;
|
||||
|
||||
using mbts.co2;
|
||||
using mbts.spark;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
[ProtoContract]
|
||||
class MarketSizeDisplay : IEquatable<MarketSizeDisplay> {
|
||||
[ProtoMember(1)]
|
||||
public readonly SizeFormattingElement[] normal;
|
||||
[ProtoMember(2)]
|
||||
public readonly SizeFormattingElement[] implied_present;
|
||||
|
||||
public MarketSizeDisplay() { }
|
||||
public MarketSizeDisplay(SizeFormattingElement[] normal, SizeFormattingElement[] implied_present) {
|
||||
this.normal = normal;
|
||||
this.implied_present = implied_present;
|
||||
}
|
||||
|
||||
public enum SizeFormattingElementType {
|
||||
Merged,
|
||||
Direct,
|
||||
Implied,
|
||||
Const,
|
||||
MergedNotional,
|
||||
}
|
||||
|
||||
public static MarketSizeDisplay get_default() =>
|
||||
new MarketSizeDisplay(SizeFormattingElement.merged, SizeFormattingElement.merged_asterisk);
|
||||
|
||||
public static bool TryParse(string expression, out List<SizeFormattingElement> elements) {
|
||||
elements = new List<SizeFormattingElement>();
|
||||
if (expression is null) {
|
||||
return false;
|
||||
}
|
||||
int tokenStart = 0;
|
||||
for (int i = 0; i < expression.Length - 3; i++) {
|
||||
if (expression[i] != '$' || expression[i + 1] != '{') {
|
||||
continue;
|
||||
}
|
||||
if (i > tokenStart) {
|
||||
elements.Add(SizeFormattingElement.Create(expression.Substring(tokenStart, i - tokenStart)));
|
||||
}
|
||||
i += 2;
|
||||
tokenStart = i;
|
||||
while (i < expression.Length && expression[i] != '}') {
|
||||
i++;
|
||||
}
|
||||
if (i == expression.Length
|
||||
|| !Enum.TryParse(expression.Substring(tokenStart, i - tokenStart), out SizeFormattingElementType typ)
|
||||
|| typ == SizeFormattingElementType.Const) {
|
||||
elements = null;
|
||||
return false;
|
||||
}
|
||||
elements.Add(SizeFormattingElement.Create(typ));
|
||||
tokenStart = i + 1;
|
||||
}
|
||||
if (tokenStart < expression.Length) {
|
||||
elements.Add(SizeFormattingElement.Create(expression.Substring(tokenStart)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public MarketSizeDisplay with(SizeFormattingElement[] normal = null, SizeFormattingElement[] implied_present = null) =>
|
||||
new MarketSizeDisplay(normal ?? this.normal, implied_present ?? this.implied_present);
|
||||
|
||||
public bool Equals(MarketSizeDisplay other) =>
|
||||
other != null &&
|
||||
EqualityComparer.safe_sequence_equals(normal, other.normal) &&
|
||||
EqualityComparer.safe_sequence_equals(implied_present, other.implied_present);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as MarketSizeDisplay);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(implied_present.GetHashCode(), normal.GetHashCode());
|
||||
|
||||
[ProtoContract]
|
||||
public struct SizeFormattingElement : IEquatable<SizeFormattingElement> {
|
||||
[ProtoMember(1)]
|
||||
public readonly SizeFormattingElementType type;
|
||||
[ProtoMember(2)]
|
||||
public readonly string data;
|
||||
|
||||
SizeFormattingElement(SizeFormattingElementType type, string data = null) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
internal static SizeFormattingElement Create(string str) =>
|
||||
new(SizeFormattingElementType.Const, str);
|
||||
|
||||
internal static SizeFormattingElement Create(SizeFormattingElementType type) =>
|
||||
new(type);
|
||||
|
||||
public bool Equals(SizeFormattingElement other) => other.type == type && string.Equals(data, other.data);
|
||||
public override bool Equals(object obj) => obj is SizeFormattingElement other && Equals(other);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(type.GetHashCode(), EqualityComparer.safe_get_hash_code(data));
|
||||
|
||||
public override string ToString() =>
|
||||
type == SizeFormattingElementType.Const
|
||||
? data ?? string.Empty
|
||||
: "${" + type.ToString() + "}";
|
||||
|
||||
public static bool operator ==(SizeFormattingElement a, SizeFormattingElement b) => a.Equals(b);
|
||||
public static bool operator !=(SizeFormattingElement a, SizeFormattingElement b) => !a.Equals(b);
|
||||
|
||||
public static SizeFormattingElement[] direct => new[] { new SizeFormattingElement(SizeFormattingElementType.Direct) };
|
||||
public static SizeFormattingElement[] implied => new[] { new SizeFormattingElement(SizeFormattingElementType.Implied) };
|
||||
public static SizeFormattingElement[] direct_implied => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Direct),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Implied),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged) };
|
||||
public static SizeFormattingElement[] merged_asterisk => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged), new SizeFormattingElement(SizeFormattingElementType.Const, "*") };
|
||||
public static SizeFormattingElement[] merged_implied => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Merged),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Implied),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged_direct => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Merged),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Direct),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged_direct_implied => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Merged),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Direct),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ":"),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Implied),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged_notional => new SizeFormattingElement[] {
|
||||
new(SizeFormattingElementType.MergedNotional)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static class MDDisplayHelper {
|
||||
/// This delegate should be created by calling <see cref="get_formatter(MarketSizeDisplay)"/>
|
||||
public delegate string SizeFormatter(double merged, double implied, double price, double notional);
|
||||
/// The private delegates below use refs bc the <see cref="SizeFormatter"/> returned by <see cref="get_formatter(MarketSizeDisplay)"/>
|
||||
/// internally uses several <see cref="_SizeFormatter"/>s to add different text components to the resultant string.
|
||||
/// The refs, therefor, avoid making extra copies of the variables.
|
||||
/// These are private members, so any code-readability cost is confined to a limited scope.
|
||||
delegate void _SizeFormatter(ref double merged, ref double implied, ref double price, ref double notional, ref StringBuilder sb);
|
||||
delegate void _SizeFormatterNPS(ref NativePriceSize nps, ref StringBuilder sb);
|
||||
|
||||
public static Func<double, double, string> get_value_formatter(
|
||||
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable notional_value_display) {
|
||||
var raw_fmt_size = get_formatter(size_display, display, notional_value_display);
|
||||
return (q, p) => raw_fmt_size(q, 0, p, double.NaN);
|
||||
}
|
||||
|
||||
public static Func<LadderPriceLevel, string> get_formatter_lpl(
|
||||
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable notional_value_display) {
|
||||
var raw_fmt_size = get_formatter(size_display, display, notional_value_display);
|
||||
return nps => raw_fmt_size(
|
||||
nps.Volume,
|
||||
0,
|
||||
nps.Price,
|
||||
nps.NotionalValue);
|
||||
}
|
||||
|
||||
public static Func<NativePriceSize, string> get_formatter_nps(
|
||||
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable notional_value_display) {
|
||||
var raw_fmt_size = get_formatter(size_display, display, notional_value_display);
|
||||
return nps => raw_fmt_size(
|
||||
nps.implied_size + nps.direct_size,
|
||||
nps.implied_size,
|
||||
nps.price,
|
||||
nps.notional_size);
|
||||
}
|
||||
|
||||
public static SizeFormatter get_formatter(
|
||||
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable notional_value_display) {
|
||||
_SizeFormatter implied_absent = _get_formatter(size_display.normal, display, notional_value_display);
|
||||
_SizeFormatter implied_present = _get_formatter(
|
||||
size_display.implied_present ?? size_display.normal, display, notional_value_display);
|
||||
return (merged, implied, price, notional) => {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
(implied > 0 ? implied_present : implied_absent)(ref merged, ref implied, ref price, ref notional, ref sb);
|
||||
return sb.ToString();
|
||||
};
|
||||
}
|
||||
|
||||
static _SizeFormatterNPS _get_formatter_nps(
|
||||
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
|
||||
TickDisplaySettingsImmutable display,
|
||||
TickDisplaySettingsImmutable price_display) {
|
||||
var formatters = fmt_elements.is_null_or_empty() ?
|
||||
Array.Empty<_SizeFormatterNPS>() :
|
||||
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter_nps(sfe, display, price_display));
|
||||
return new _SizeFormatterNPS((ref NativePriceSize nps, ref StringBuilder sb) => {
|
||||
foreach (var f in formatters)
|
||||
f(ref nps, ref sb);
|
||||
});
|
||||
}
|
||||
|
||||
static _SizeFormatter _get_formatter(
|
||||
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
|
||||
TickDisplaySettingsImmutable display,
|
||||
TickDisplaySettingsImmutable notional_value_display) {
|
||||
_SizeFormatter[] formatters = fmt_elements.is_null_or_empty() ?
|
||||
Array.Empty<_SizeFormatter>() :
|
||||
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter(sfe, display, notional_value_display));
|
||||
return new _SizeFormatter((ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) => {
|
||||
foreach (var f in formatters)
|
||||
f(ref m, ref i, ref p, ref n, ref sb);
|
||||
});
|
||||
}
|
||||
|
||||
static _SizeFormatterNPS _get_formatter_nps(
|
||||
MarketSizeDisplay.SizeFormattingElement fmt_element,
|
||||
TickDisplaySettingsImmutable display,
|
||||
TickDisplaySettingsImmutable price_display) =>
|
||||
fmt_element.type switch {
|
||||
MarketSizeDisplay.SizeFormattingElementType.Const =>
|
||||
SizeTokenFormatters.char_formatter_nps(fmt_element.data ?? string.Empty),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Direct =>
|
||||
SizeTokenFormatters.direct_formatter_nps(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Implied =>
|
||||
SizeTokenFormatters.implied_formatter_nps(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Merged =>
|
||||
SizeTokenFormatters.merged_formatter_nps(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.MergedNotional =>
|
||||
SizeTokenFormatters.merged_notional_formatter_nps(price_display),
|
||||
_ => throw ExceptionBuilder.Argument.unexpected_case(
|
||||
fmt_element.type, nameof(fmt_element.type)),
|
||||
};
|
||||
|
||||
static _SizeFormatter _get_formatter(
|
||||
MarketSizeDisplay.SizeFormattingElement fmt_element,
|
||||
TickDisplaySettingsImmutable display,
|
||||
TickDisplaySettingsImmutable notional_value_display) =>
|
||||
fmt_element.type switch {
|
||||
MarketSizeDisplay.SizeFormattingElementType.Const =>
|
||||
SizeTokenFormatters.char_formatter(fmt_element.data ?? string.Empty),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Direct =>
|
||||
SizeTokenFormatters.direct_formatter(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Implied =>
|
||||
SizeTokenFormatters.implied_formatter(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Merged =>
|
||||
SizeTokenFormatters.merged_formatter(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.MergedNotional =>
|
||||
SizeTokenFormatters.merged_notional_formatter(notional_value_display),
|
||||
_ => throw ExceptionBuilder.Argument.unexpected_case(
|
||||
fmt_element.type, nameof(fmt_element.type)),
|
||||
};
|
||||
|
||||
static class SizeTokenFormatters {
|
||||
public static _SizeFormatter char_formatter(string data) {
|
||||
string local = data.ToString();
|
||||
return (ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
|
||||
sb.Append(local);
|
||||
}
|
||||
public static _SizeFormatter merged_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
|
||||
sb.Append(display.format(m));
|
||||
|
||||
public static _SizeFormatter direct_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
|
||||
sb.Append(display.format(m - i));
|
||||
|
||||
public static _SizeFormatter implied_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
|
||||
sb.Append(display.format(i));
|
||||
|
||||
public static _SizeFormatter merged_notional_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref double p, ref double n, ref StringBuilder sb) =>
|
||||
sb.Append(display.format(n));
|
||||
|
||||
public static _SizeFormatterNPS char_formatter_nps(string data) {
|
||||
string local = data.ToString();
|
||||
return new _SizeFormatterNPS((ref NativePriceSize _, ref StringBuilder sb) => sb.Append(local));
|
||||
}
|
||||
|
||||
public static _SizeFormatterNPS merged_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format((double)nps.size));
|
||||
|
||||
public static _SizeFormatterNPS direct_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format((double)nps.direct_size));
|
||||
|
||||
public static _SizeFormatterNPS implied_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format((double)nps.implied_size));
|
||||
|
||||
public static _SizeFormatterNPS merged_notional_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format(nps.notional_size));
|
||||
}
|
||||
}
|
||||
|
||||
class MarketSizeDisplayVM : ViewModelBase, IValueFactory<MarketSizeDisplay> {
|
||||
readonly IEqualityComparer<MarketSizeDisplay.SizeFormattingElement[]> _comp = EqualityComparer.Create<MarketSizeDisplay.SizeFormattingElement[]>((a, b) => EqualityComparer.safe_sequence_equals(a, b));
|
||||
readonly Property<string> _selected_format, _selected_alternate_implied_format;
|
||||
|
||||
public MarketSizeDisplayVM() {
|
||||
_selected_format = backing<string>(nameof(selected_format));
|
||||
_selected_alternate_implied_format = backing<string>(nameof(selected_alternate_implied_format));
|
||||
sample_items = new[] {
|
||||
new SampleItem(90, 0, this),
|
||||
new SampleItem(75, 25, this)
|
||||
};
|
||||
_selected_format.Changed += refresh_sample_items;
|
||||
_selected_alternate_implied_format.Changed += refresh_sample_items;
|
||||
void refresh_sample_items(string oldval, string newval) {
|
||||
foreach (var si in sample_items)
|
||||
si.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> format_options { get; } = new[] {
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.direct),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.implied),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.direct_implied),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_implied),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_direct_implied),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_notional),
|
||||
};
|
||||
|
||||
public IEnumerable<string> alternate_implied_format_options { get; } = new[] {
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_asterisk),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_implied),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_direct),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_direct_implied),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_notional),
|
||||
};
|
||||
|
||||
public string selected_format {
|
||||
get => _selected_format.Value;
|
||||
set => _selected_format.Value = value;
|
||||
}
|
||||
public string selected_alternate_implied_format {
|
||||
get => _selected_alternate_implied_format.Value;
|
||||
set => _selected_alternate_implied_format.Value = value;
|
||||
}
|
||||
|
||||
public IEnumerable<SampleItem> sample_items { get; }
|
||||
|
||||
public MarketSizeDisplay create() {
|
||||
var normal_fmt = selected_format;
|
||||
var alt_implied_fmt = selected_alternate_implied_format;
|
||||
if (!MarketSizeDisplay.TryParse(normal_fmt, out var normal_els)) {
|
||||
normal_els = new(MarketSizeDisplay.SizeFormattingElement.merged);
|
||||
}
|
||||
if (!MarketSizeDisplay.TryParse(alt_implied_fmt, out var alt_implied_els)) {
|
||||
alt_implied_els = new(normal_els);
|
||||
}
|
||||
return new MarketSizeDisplay(normal_els.ToArray(), alt_implied_els.ToArray());
|
||||
}
|
||||
|
||||
public void load_from(MarketSizeDisplay msd) {
|
||||
selected_format = string.Concat(msd.normal.empty_if_null());
|
||||
selected_alternate_implied_format = string.Concat(msd.implied_present.empty_if_null());
|
||||
}
|
||||
|
||||
public class SampleItem : ViewModelBase {
|
||||
readonly DelegateProperty<string> _display;
|
||||
|
||||
public SampleItem(uint direct, uint implied, MarketSizeDisplayVM owner) {
|
||||
this.direct = direct;
|
||||
this.implied = implied;
|
||||
_display = backing(nameof(display), fmt);
|
||||
|
||||
string fmt() {
|
||||
const double SAMPLE_PRICE = 25.0;
|
||||
var impl = MDDisplayHelper.get_formatter(
|
||||
owner.create(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
|
||||
return impl(merged, implied, 1, merged * SAMPLE_PRICE);
|
||||
}
|
||||
}
|
||||
|
||||
public uint direct { get; }
|
||||
public uint implied { get; }
|
||||
public uint merged => direct + implied;
|
||||
public string display => _display.Value;
|
||||
public void refresh() => _display.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.api.domain;
|
||||
using bts.utils;
|
||||
|
||||
using mbts.co2;
|
||||
using mbts.spark;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
[ProtoContract]
|
||||
class MarketSizeDisplay : IEquatable<MarketSizeDisplay> {
|
||||
[ProtoMember(1)]
|
||||
public readonly SizeFormattingElement[] normal;
|
||||
[ProtoMember(2)]
|
||||
public readonly SizeFormattingElement[] implied_present;
|
||||
|
||||
public MarketSizeDisplay() { }
|
||||
public MarketSizeDisplay(SizeFormattingElement[] normal, SizeFormattingElement[] implied_present) {
|
||||
this.normal = normal;
|
||||
this.implied_present = implied_present;
|
||||
}
|
||||
|
||||
public enum SizeFormattingElementType {
|
||||
Merged,
|
||||
Direct,
|
||||
Implied,
|
||||
Const
|
||||
}
|
||||
|
||||
public static MarketSizeDisplay get_default() =>
|
||||
new MarketSizeDisplay(SizeFormattingElement.merged, SizeFormattingElement.merged_asterisk);
|
||||
|
||||
public MarketSizeDisplay with(SizeFormattingElement[] normal = null, SizeFormattingElement[] implied_present = null) =>
|
||||
new MarketSizeDisplay(normal ?? this.normal, implied_present ?? this.implied_present);
|
||||
|
||||
public bool Equals(MarketSizeDisplay other) =>
|
||||
other != null &&
|
||||
EqualityComparer.safe_sequence_equals(normal, other.normal) &&
|
||||
EqualityComparer.safe_sequence_equals(implied_present, other.implied_present);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as MarketSizeDisplay);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(implied_present.GetHashCode(), normal.GetHashCode());
|
||||
|
||||
[ProtoContract]
|
||||
public struct SizeFormattingElement : IEquatable<SizeFormattingElement> {
|
||||
[ProtoMember(1)]
|
||||
public readonly SizeFormattingElementType type;
|
||||
[ProtoMember(2)]
|
||||
public readonly string data;
|
||||
|
||||
SizeFormattingElement(SizeFormattingElementType type, string data = null) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public bool Equals(SizeFormattingElement other) => other.type == type && string.Equals(data, other.data);
|
||||
public override bool Equals(object obj) => obj is SizeFormattingElement other && Equals(other);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(type.GetHashCode(), EqualityComparer.safe_get_hash_code(data));
|
||||
public override string ToString() => type == SizeFormattingElementType.Const ? data ?? string.Empty : type.ToString();
|
||||
|
||||
public static bool operator ==(SizeFormattingElement a, SizeFormattingElement b) => a.Equals(b);
|
||||
public static bool operator !=(SizeFormattingElement a, SizeFormattingElement b) => !a.Equals(b);
|
||||
|
||||
public static SizeFormattingElement[] direct => new[] { new SizeFormattingElement(SizeFormattingElementType.Direct) };
|
||||
public static SizeFormattingElement[] implied => new[] { new SizeFormattingElement(SizeFormattingElementType.Implied) };
|
||||
public static SizeFormattingElement[] direct_implied => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Direct),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Implied),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged) };
|
||||
public static SizeFormattingElement[] merged_asterisk => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged), new SizeFormattingElement(SizeFormattingElementType.Const, "*") };
|
||||
public static SizeFormattingElement[] merged_implied => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Merged),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Implied),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged_direct => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Merged),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Direct),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged_direct_implied => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Merged),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Direct),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ":"),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Implied),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static class MDDisplayHelper {
|
||||
/// This delegate should be created by calling <see cref="get_formatter(MarketSizeDisplay)"/>
|
||||
public delegate string SizeFormatter(double merged, double implied);
|
||||
/// The private delegates below use refs bc the <see cref="SizeFormatter"/> returned by <see cref="get_formatter(MarketSizeDisplay)"/>
|
||||
/// internally uses several <see cref="_SizeFormatter"/>s to add different text components to the resultant string.
|
||||
/// The refs, therefor, avoid making extra copies of the variables.
|
||||
/// These are private members, so any code-readability cost is confined to a limited scope.
|
||||
delegate void _SizeFormatter(ref double merged, ref double implied, ref StringBuilder sb);
|
||||
delegate void _SizeFormatterNPS(ref NativePriceSize nps, ref StringBuilder sb);
|
||||
|
||||
public static Func<double, string> get_value_formatter(double divisor) =>
|
||||
value => (value / divisor).ToString();
|
||||
|
||||
public static Func<double, string> get_value_formatter(
|
||||
TickDisplaySettingsImmutable display) =>
|
||||
v => display.format(v);
|
||||
|
||||
public static Func<NativePriceSize, string> get_formatter_nps(
|
||||
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display) {
|
||||
var raw_fmt_size = get_formatter(size_display, display);
|
||||
return nps => raw_fmt_size(
|
||||
nps.implied_size + nps.direct_size,
|
||||
nps.implied_size);
|
||||
}
|
||||
|
||||
public static SizeFormatter get_formatter(MarketSizeDisplay size_display, TickDisplaySettingsImmutable display) {
|
||||
_SizeFormatter implied_absent = _get_formatter(size_display.normal, display);
|
||||
_SizeFormatter implied_present = _get_formatter(
|
||||
size_display.implied_present ?? size_display.normal, display);
|
||||
return (merged, implied) => {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
(implied > 0 ? implied_present : implied_absent)(ref merged, ref implied, ref sb);
|
||||
return sb.ToString();
|
||||
};
|
||||
}
|
||||
|
||||
static _SizeFormatterNPS _get_formatter_nps(
|
||||
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
|
||||
TickDisplaySettingsImmutable display) {
|
||||
var formatters = fmt_elements.is_null_or_empty() ?
|
||||
Array.Empty<_SizeFormatterNPS>() :
|
||||
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter_nps(sfe, display));
|
||||
return new _SizeFormatterNPS((ref NativePriceSize nps, ref StringBuilder sb) => {
|
||||
foreach (var f in formatters)
|
||||
f(ref nps, ref sb);
|
||||
});
|
||||
}
|
||||
|
||||
static _SizeFormatter _get_formatter(
|
||||
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
|
||||
TickDisplaySettingsImmutable display) {
|
||||
_SizeFormatter[] formatters = fmt_elements.is_null_or_empty() ?
|
||||
Array.Empty<_SizeFormatter>() :
|
||||
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter(sfe, display));
|
||||
return new _SizeFormatter((ref double m, ref double i, ref StringBuilder sb) => {
|
||||
foreach (var f in formatters)
|
||||
f(ref m, ref i, ref sb);
|
||||
});
|
||||
}
|
||||
|
||||
static _SizeFormatterNPS _get_formatter_nps(
|
||||
MarketSizeDisplay.SizeFormattingElement fmt_element,
|
||||
TickDisplaySettingsImmutable display) =>
|
||||
fmt_element.type switch {
|
||||
MarketSizeDisplay.SizeFormattingElementType.Const =>
|
||||
SizeTokenFormatters.char_formatter_nps(fmt_element.data ?? string.Empty),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Direct =>
|
||||
SizeTokenFormatters.direct_formatter_nps(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Implied =>
|
||||
SizeTokenFormatters.implied_formatter_nps(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Merged =>
|
||||
SizeTokenFormatters.merged_formatter_nps(display),
|
||||
_ => throw ExceptionBuilder.Argument.unexpected_case(
|
||||
fmt_element.type, nameof(fmt_element.type)),
|
||||
};
|
||||
|
||||
static _SizeFormatter _get_formatter(
|
||||
MarketSizeDisplay.SizeFormattingElement fmt_element,
|
||||
TickDisplaySettingsImmutable display) =>
|
||||
fmt_element.type switch {
|
||||
MarketSizeDisplay.SizeFormattingElementType.Const =>
|
||||
SizeTokenFormatters.char_formatter(fmt_element.data ?? string.Empty),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Direct =>
|
||||
SizeTokenFormatters.direct_formatter(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Implied =>
|
||||
SizeTokenFormatters.implied_formatter(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Merged =>
|
||||
SizeTokenFormatters.merged_formatter(display),
|
||||
_ => throw ExceptionBuilder.Argument.unexpected_case(
|
||||
fmt_element.type, nameof(fmt_element.type)),
|
||||
};
|
||||
|
||||
static class SizeTokenFormatters {
|
||||
public static _SizeFormatter char_formatter(string data) {
|
||||
string local = data.ToString();
|
||||
return new _SizeFormatter((ref double m, ref double i, ref StringBuilder sb) => sb.Append(local));
|
||||
}
|
||||
public static _SizeFormatter merged_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref StringBuilder sb) => sb.Append(display.format(m));
|
||||
|
||||
public static _SizeFormatter direct_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref StringBuilder sb) => sb.Append(display.format(m - i));
|
||||
|
||||
public static _SizeFormatter implied_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref StringBuilder sb) => sb.Append(display.format(i));
|
||||
|
||||
public static _SizeFormatterNPS char_formatter_nps(string data) {
|
||||
string local = data.ToString();
|
||||
return new _SizeFormatterNPS((ref NativePriceSize _, ref StringBuilder sb) => sb.Append(local));
|
||||
}
|
||||
|
||||
public static _SizeFormatterNPS merged_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format((double)nps.size));
|
||||
|
||||
public static _SizeFormatterNPS direct_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format((double)nps.direct_size));
|
||||
|
||||
public static _SizeFormatterNPS implied_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format((double)nps.implied_size));
|
||||
}
|
||||
}
|
||||
|
||||
class MarketSizeDisplayVM : ViewModelBase, IValueFactory<MarketSizeDisplay> {
|
||||
readonly IEqualityComparer<MarketSizeDisplay.SizeFormattingElement[]> _comp = EqualityComparer.Create<MarketSizeDisplay.SizeFormattingElement[]>((a, b) => EqualityComparer.safe_sequence_equals(a, b));
|
||||
readonly Property<FormattingVM> _selected_format, _selected_alternate_implied_format;
|
||||
readonly DelegateProperty<bool> _can_choose_alternate_implied_format;
|
||||
|
||||
public MarketSizeDisplayVM() {
|
||||
_selected_format = backing<FormattingVM>(nameof(selected_format));
|
||||
_selected_alternate_implied_format = backing<FormattingVM>(nameof(selected_alternate_implied_format));
|
||||
_can_choose_alternate_implied_format = backing(nameof(can_choose_alternate_implied_format), () =>
|
||||
_comp.Equals(MarketSizeDisplay.SizeFormattingElement.merged, selected_format?.value));
|
||||
sample_items = new[] {
|
||||
new SampleItem(90, 0, this),
|
||||
new SampleItem(75, 25, this)
|
||||
};
|
||||
_selected_format.Changed += refresh_sample_items;
|
||||
_selected_alternate_implied_format.Changed += refresh_sample_items;
|
||||
void refresh_sample_items(FormattingVM oldval, FormattingVM newval) {
|
||||
_can_choose_alternate_implied_format.refresh();
|
||||
foreach (var si in sample_items)
|
||||
si.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<FormattingVM> format_options { get; } = new[] {
|
||||
MarketSizeDisplay.SizeFormattingElement.direct,
|
||||
MarketSizeDisplay.SizeFormattingElement.implied,
|
||||
MarketSizeDisplay.SizeFormattingElement.direct_implied,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged_implied,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged_direct_implied,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged
|
||||
}.Select(ts => new FormattingVM(ts)).ToArray();
|
||||
|
||||
public IEnumerable<FormattingVM> alternate_implied_format_options { get; } = new[] {
|
||||
MarketSizeDisplay.SizeFormattingElement.merged,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged_asterisk,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged_implied,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged_direct,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged_direct_implied
|
||||
}.Select(ts => new FormattingVM(ts)).ToArray();
|
||||
|
||||
public FormattingVM selected_format {
|
||||
get => _selected_format.Value;
|
||||
set => _selected_format.Value = value;
|
||||
}
|
||||
public FormattingVM selected_alternate_implied_format {
|
||||
get => _selected_alternate_implied_format.Value;
|
||||
set => _selected_alternate_implied_format.Value = value;
|
||||
}
|
||||
|
||||
public bool can_choose_alternate_implied_format => _can_choose_alternate_implied_format.Value;
|
||||
|
||||
public IEnumerable<SampleItem> sample_items { get; }
|
||||
|
||||
public MarketSizeDisplay create() {
|
||||
var normal_fmt = selected_format?.value;
|
||||
var alt_implied_fmt = can_choose_alternate_implied_format ? selected_alternate_implied_format?.value : normal_fmt;
|
||||
return new MarketSizeDisplay(normal_fmt, alt_implied_fmt);
|
||||
}
|
||||
|
||||
public void load_from(MarketSizeDisplay msd) {
|
||||
selected_format = format_options.FirstOrDefault(f => _comp.Equals(f.value, msd.normal));
|
||||
if (can_choose_alternate_implied_format)
|
||||
selected_alternate_implied_format = alternate_implied_format_options.FirstOrDefault(f => _comp.Equals(f.value, msd.implied_present));
|
||||
}
|
||||
|
||||
public class FormattingVM : ViewModelBase, IEquatable<FormattingVM> {
|
||||
public FormattingVM(MarketSizeDisplay.SizeFormattingElement[] tokens) =>
|
||||
value = tokens;
|
||||
|
||||
public MarketSizeDisplay.SizeFormattingElement[] value { get; }
|
||||
|
||||
public bool Equals(FormattingVM other) =>
|
||||
other != null &&
|
||||
EqualityComparer.safe_sequence_equals(value, other.value);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FormattingVM);
|
||||
public override int GetHashCode() => EqualityComparer.safe_aggregate_hash(value);
|
||||
public override string ToString() => string.Join(string.Empty, value);
|
||||
}
|
||||
|
||||
public class SampleItem : ViewModelBase {
|
||||
readonly DelegateProperty<string> _display;
|
||||
|
||||
public SampleItem(uint direct, uint implied, MarketSizeDisplayVM owner) {
|
||||
this.direct = direct;
|
||||
this.implied = implied;
|
||||
_display = backing(nameof(display), fmt);
|
||||
|
||||
string fmt() {
|
||||
var impl = MDDisplayHelper.get_formatter(
|
||||
owner.create(), Security.Defaults.quantity_display_settingsi);
|
||||
return impl(merged, implied);
|
||||
}
|
||||
}
|
||||
|
||||
public uint direct { get; }
|
||||
public uint implied { get; }
|
||||
public uint merged => direct + implied;
|
||||
public string display => _display.Value;
|
||||
public void refresh() => _display.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,6 @@ namespace mbts.co2 {
|
||||
public Size size => _size;
|
||||
public Size direct_size => _direct_size;
|
||||
public Size implied_size => _size - _direct_size;
|
||||
public double notional_size => price * size;
|
||||
public double price => _price.price();
|
||||
public bool is_nan => _price.is_nan;
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace mbts {
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
public readonly struct SecurityMD : IEquatable<SecurityMD> {
|
||||
public static readonly Func<SecurityMD, uint> key_fn = smd => smd.security.Id;
|
||||
@@ -61,128 +60,4 @@ namespace mbts {
|
||||
public static bool operator !=(SecurityMD left, SecurityMD right) =>
|
||||
!(left == right);
|
||||
}
|
||||
|
||||
static class SecurityMDExtensions {
|
||||
const uint DEPTH_COUNT = 10;
|
||||
[ThreadStatic]
|
||||
static NativePriceSize[] leg1Bids;
|
||||
|
||||
[ThreadStatic]
|
||||
static NativePriceSize[] leg1Asks;
|
||||
|
||||
static SecurityMDExtensions() {
|
||||
leg1Bids = new NativePriceSize[DEPTH_COUNT];
|
||||
leg1Asks = new NativePriceSize[DEPTH_COUNT];
|
||||
}
|
||||
|
||||
public static Book Depth(this SecurityMD security, IReadOnlyDictionary<uint, SecurityMD> securities, ref ulong time) {
|
||||
var bids = new NativePriceSize[DEPTH_COUNT];
|
||||
var asks = new NativePriceSize[DEPTH_COUNT];
|
||||
var bidsOut = new (double, double, double)[(int)DEPTH_COUNT];
|
||||
var asksOut = new (double, double, double)[(int)DEPTH_COUNT];
|
||||
if (!security.security.HasSynthetic) {
|
||||
security.md.get_depth(ref time, ref bids, ref asks, DEPTH_COUNT);
|
||||
for (int i = 0; i < DEPTH_COUNT; i++) {
|
||||
bidsOut[i] = (bids[i].price, bids[i].size, bids[i].notional_size);
|
||||
asksOut[i] = (asks[i].price, asks[i].size, asks[i].notional_size);
|
||||
}
|
||||
return new(bidsOut, asksOut);
|
||||
}
|
||||
Log.assert(security.security.Synthetic.Legs.TryGetFirst(out var leg1));
|
||||
if (!securities.TryGetValue(leg1.Security.Id, out var sec1)) {
|
||||
return Book.Empty;
|
||||
}
|
||||
ulong sec1Time = 0;
|
||||
if (!sec1.md.get_depth(ref sec1Time, ref leg1Bids, ref leg1Asks, DEPTH_COUNT)) {
|
||||
return Book.Empty;
|
||||
}
|
||||
security.md.get_depth(ref time, ref bids, ref asks, DEPTH_COUNT);
|
||||
int leg1BidsLevel = 0;
|
||||
int leg1AsksLevel = 0;
|
||||
double leg1BidsConsumed = 0;
|
||||
double leg1AsksConsumed = 0;
|
||||
double takeQuantity;
|
||||
double notional;
|
||||
for (int i = 0; i < DEPTH_COUNT && !bids[i].is_nan; i++) {
|
||||
takeQuantity = bids[i].size * leg1.Quantity;
|
||||
notional = getNotionalValue(ref leg1Bids, ref leg1BidsLevel, ref leg1BidsConsumed, ref takeQuantity);
|
||||
bidsOut[i] = (bids[i].price, bids[i].size, notional);
|
||||
takeQuantity = asks[i].size * leg1.Quantity;
|
||||
notional = getNotionalValue(ref leg1Asks, ref leg1AsksLevel, ref leg1AsksConsumed, ref takeQuantity);
|
||||
asksOut[i] = (asks[i].price, asks[i].size, notional);
|
||||
}
|
||||
return new(bidsOut, asksOut);
|
||||
}
|
||||
|
||||
public static Book Depth(this SecurityMD security, NativePriceSize[] bids, NativePriceSize[] asks, IReadOnlyDictionary<uint, SecurityMD> securities) {
|
||||
bids ??= new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
|
||||
asks ??= new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
|
||||
var bidsOut = new (double, double, double)[bids.Length];
|
||||
var asksOut = new (double, double, double)[asks.Length];
|
||||
var depthCount = (uint)Math.Min(bids.Length, asks.Length);
|
||||
if (!security.security.HasSynthetic) {
|
||||
for (int i = 0; i < depthCount; i++) {
|
||||
bidsOut[i] = (bids[i].price, bids[i].size, bids[i].notional_size);
|
||||
asksOut[i] = (asks[i].price, asks[i].size, asks[i].notional_size);
|
||||
}
|
||||
return new(bidsOut, asksOut);
|
||||
}
|
||||
Log.assert(security.security.Synthetic.Legs.TryGetFirst(out var leg1));
|
||||
if (!securities.TryGetValue(leg1.Security.Id, out var sec1)) {
|
||||
return Book.Empty;
|
||||
}
|
||||
ulong sec1Time = 0;
|
||||
if (!sec1.md.get_depth(ref sec1Time, ref leg1Bids, ref leg1Asks, depthCount)) {
|
||||
return Book.Empty;
|
||||
}
|
||||
int leg1BidsLevel = 0;
|
||||
int leg1AsksLevel = 0;
|
||||
double leg1BidsConsumed = 0;
|
||||
double leg1AsksConsumed = 0;
|
||||
double takeQuantity;
|
||||
double notional;
|
||||
for (int i = 0; i < depthCount && !bids[i].is_nan; i++) {
|
||||
takeQuantity = bids[i].size;
|
||||
notional = getNotionalValue(ref leg1Bids, ref leg1BidsLevel, ref leg1BidsConsumed, ref takeQuantity);
|
||||
bidsOut[i] = (bids[i].price, bids[i].size, notional);
|
||||
takeQuantity = asks[i].size;
|
||||
notional = getNotionalValue(ref leg1Asks, ref leg1AsksLevel, ref leg1AsksConsumed, ref takeQuantity);
|
||||
asksOut[i] = (asks[i].price, asks[i].size, notional);
|
||||
}
|
||||
return new(bidsOut, asksOut);
|
||||
}
|
||||
|
||||
static double getNotionalValue(ref NativePriceSize[] depth, ref int level, ref double levelConsumed, ref double takeQuantity) {
|
||||
if (level >= depth.Length) {
|
||||
return double.NaN;
|
||||
}
|
||||
double notionalValue = 0;
|
||||
while (takeQuantity > MathUtils.epsilon<double>() && level < depth.Length && !depth[level].is_nan) {
|
||||
var lvlDepth = (double)depth[level].size - levelConsumed;
|
||||
if (lvlDepth <= 0) {
|
||||
level++;
|
||||
levelConsumed = 0;
|
||||
continue;
|
||||
}
|
||||
var lvlTake = Math.Min(takeQuantity, lvlDepth);
|
||||
notionalValue += lvlTake * depth[level].price;
|
||||
levelConsumed += lvlTake;
|
||||
takeQuantity -= lvlDepth;
|
||||
}
|
||||
return notionalValue;
|
||||
}
|
||||
|
||||
public class Book {
|
||||
public Book(IEnumerable<(double Price, double Volume, double NotionalValue)> bids, IEnumerable<(double Price, double Quantity, double NotionalValue)> asks) {
|
||||
Bids = bids;
|
||||
Asks = asks;
|
||||
}
|
||||
|
||||
public static Book Empty =>
|
||||
new(Array.Empty<(double, double, double)>(), Array.Empty<(double, double, double)>());
|
||||
|
||||
public IEnumerable<(double Price, double Volume, double NotionalValue)> Bids { get; }
|
||||
public IEnumerable<(double Price, double Volume, double NotionalValue)> Asks { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,20 +69,6 @@ namespace Spark.Api.Orders {
|
||||
public uint security_id => order.security_id;
|
||||
public SecurityImmutable? security => order.security;
|
||||
|
||||
// TODO: remove assumption that synthetic quantities are already notional values
|
||||
public double notional_value =>
|
||||
(security?.HasSynthetic ?? false)
|
||||
&& security.Value.Synthetic.PriceCalc == SyntheticContract.Types.PriceCalc.NotionalLeg
|
||||
? order.order.quantity()
|
||||
: order.order.notional_value();
|
||||
|
||||
// TODO: remove assumption that synthetic quantities are already notional values
|
||||
public double filled_notional_value =>
|
||||
(security?.HasSynthetic ?? false)
|
||||
&& security.Value.Synthetic.PriceCalc == SyntheticContract.Types.PriceCalc.NotionalLeg
|
||||
? order.order.filled_quantity()
|
||||
: order.order.filled_notional_value();
|
||||
|
||||
public bool Equals(OrderRecordTupleWithRoot other) =>
|
||||
order.Equals(other.order) &&
|
||||
EqualityComparer.nullable_equals(_root, other._root);
|
||||
|
||||
@@ -48,12 +48,6 @@ namespace Spark.Api.Orders {
|
||||
public static double quantity(this OrderRecordImmutable order) =>
|
||||
order.HasState ? order.State.quantity() : 0;
|
||||
|
||||
public static double notional_value(this OrderRecordImmutable order) =>
|
||||
order.quantity() * order.ui_price();
|
||||
|
||||
public static double filled_notional_value(this OrderRecordImmutable order) =>
|
||||
order.avg_price() * order.filled_quantity();
|
||||
|
||||
public static double working_quantity(this OrderRecordImmutable order) =>
|
||||
order.HasState ? order.State.working_quantity() : 0;
|
||||
|
||||
@@ -493,8 +487,5 @@ namespace Spark.Api.Orders {
|
||||
CustomOrderDefinition.CustomOneofCase.Composite => "COMP",
|
||||
_ => algo_type.ToString(),
|
||||
};
|
||||
|
||||
public static double notional_value(this FillImmutable fill) =>
|
||||
fill.LastShares * fill.LastPrice;
|
||||
}
|
||||
}
|
||||
@@ -10,64 +10,58 @@ using System.IO;
|
||||
using System.Reactive.Disposables;
|
||||
using mbts;
|
||||
using System.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
||||
namespace bts.api.core {
|
||||
|
||||
internal class QuickbusClientChannel : ClientChannel {
|
||||
IntPtr _client;
|
||||
internal class QuickbusClientChannel : ClientChannel {
|
||||
IntPtr _client;
|
||||
|
||||
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
|
||||
Log.QBus.diag($"Created QuickbusClientChannel");
|
||||
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
|
||||
}
|
||||
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
|
||||
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
Log.QBus.diag($"Disposed QuickbusClientChannel");
|
||||
ApiNativeMethods.api_client_destroy(_client);
|
||||
_client = IntPtr.Zero;
|
||||
}
|
||||
public override void Dispose() {
|
||||
ApiNativeMethods.api_client_destroy(_client);
|
||||
_client = IntPtr.Zero;
|
||||
}
|
||||
|
||||
|
||||
int _req_id = 0;
|
||||
Dictionary<int, PBCallbackWrapper> _keepalive_cache = new();
|
||||
|
||||
unsafe public override Task<Result<Response>> send<Request, Response>(int method_id, Request request) {
|
||||
var id = Interlocked.Increment(ref _req_id);
|
||||
var header = new RequestHeader() {
|
||||
Method = method_id,
|
||||
RequestId = id,
|
||||
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
|
||||
};
|
||||
var buffer = header.serialize(request);
|
||||
var tcs = new TaskCompletionSource<Result<Response>>();
|
||||
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
|
||||
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
|
||||
if (res.header.Code == ApiError.Success) {
|
||||
tcs.TrySetResult(new Result<Response>(res.body));
|
||||
}
|
||||
else {
|
||||
tcs.TrySetResult(new Result<Response>(res.header.Code));
|
||||
}
|
||||
lock (_keepalive_cache) {
|
||||
_keepalive_cache.Remove(id);
|
||||
}
|
||||
};
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
|
||||
var cb_wrapper = new PBCallbackWrapper(callback);
|
||||
lock (_keepalive_cache) {
|
||||
_keepalive_cache.Add(id, cb_wrapper);
|
||||
var id = Interlocked.Increment(ref _req_id);
|
||||
var header = new RequestHeader() {
|
||||
Method = method_id,
|
||||
RequestId = id,
|
||||
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
|
||||
};
|
||||
var buffer = header.serialize(request);
|
||||
var tcs = new TaskCompletionSource<Result<Response>>();
|
||||
//ApiNativeMethods.api_client_log_error($"Call {id}");
|
||||
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
|
||||
//ApiNativeMethods.api_client_log_error($"Reply {id}");
|
||||
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
|
||||
if (res.header.Code == ApiError.Success) {
|
||||
tcs.TrySetResult(new Result<Response>(res.body));
|
||||
}
|
||||
ApiNativeMethods.api_client_send(
|
||||
_client, buffer, buffer.Length, cb_wrapper.Callback, IntPtr.Zero);
|
||||
// Return a task that will free the handle once the task is completed
|
||||
return tcs.Task.ContinueWith(task => {
|
||||
cb_wrapper.Free(); // Ensure the GCHandle is freed when the callback is done
|
||||
return task.Result;
|
||||
});
|
||||
}
|
||||
else {
|
||||
tcs.TrySetResult(new Result<Response>(res.header.Code));
|
||||
}
|
||||
lock (_keepalive_cache) {
|
||||
_keepalive_cache.Remove(id);
|
||||
}
|
||||
};
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
|
||||
var cb_wrapper = new PBCallbackWrapper(callback);
|
||||
lock (_keepalive_cache) {
|
||||
_keepalive_cache.Add(id, cb_wrapper);
|
||||
}
|
||||
ApiNativeMethods.api_client_send(
|
||||
_client, buffer, buffer.Length, cb_wrapper.callback, IntPtr.Zero);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public override IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(int method_id, string method, string qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter) {
|
||||
var result = Observable.Create<RawUpdate<TOut>>(async observer => {
|
||||
@@ -212,29 +206,11 @@ namespace bts.api.core {
|
||||
/// garbage-collector race may yield the infamous error, "A callback was made on a garbage
|
||||
/// collected delegate of type...").
|
||||
/// </remarks>
|
||||
class PBCallbackWrapper
|
||||
{
|
||||
public readonly ApiNativeMethods.raw_pb_callback Callback;
|
||||
private GCHandle _handle;
|
||||
class PBCallbackWrapper {
|
||||
public readonly ApiNativeMethods.raw_pb_callback callback;
|
||||
|
||||
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback)
|
||||
{
|
||||
// Pin the callback to prevent garbage collection
|
||||
Log.QBus.diag($"Created PBCallbackWrapper");
|
||||
_handle = GCHandle.Alloc(callback);
|
||||
Callback = callback;
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
// Free the GCHandle when the delegate is no longer needed
|
||||
Log.QBus.diag($"Deleting PBCallbackWrapper");
|
||||
|
||||
if (_handle.IsAllocated)
|
||||
{
|
||||
_handle.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback) =>
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,6 @@ namespace mbts.user.settings {
|
||||
public RegionalTickFactors()
|
||||
: this(new TickFactors(), new TickFactors(), double.MinValue) { }
|
||||
|
||||
public RegionalTickFactors(TickFactors tick)
|
||||
: this(tick, tick, double.PositiveInfinity) { }
|
||||
|
||||
public RegionalTickFactors(TickFactors low_tick, TickFactors high_tick, double low_tick_inclusive_end) {
|
||||
_low_tick = low_tick;
|
||||
_high_tick = high_tick;
|
||||
|
||||
@@ -78,16 +78,14 @@ namespace mbts.user.settings {
|
||||
bool? show_commas = null,
|
||||
string? nan_text = null,
|
||||
bool? show_decimal_part = null,
|
||||
bool? show_zero_integer = null,
|
||||
TickFactors? tick_factors = null) =>
|
||||
bool? show_zero_integer = null) =>
|
||||
new() {
|
||||
pad_with_leading_zeros = pad_with_leading_zeros ?? settings.pad_with_leading_zeros,
|
||||
display_multiplier = display_multiplier ?? settings.display_multiplier,
|
||||
show_commas = show_commas ?? settings.show_commas,
|
||||
nan_text = nan_text ?? settings.nan_text,
|
||||
show_decimal_part = show_decimal_part ?? settings.show_decimal_part,
|
||||
show_zero_integer = show_zero_integer ?? settings.show_zero_integer,
|
||||
tick_factors = tick_factors ?? settings.tick_factors,
|
||||
show_zero_integer = show_zero_integer ?? settings.show_zero_integer
|
||||
};
|
||||
|
||||
public static double to_display_value(this TickDisplaySettings settings, double value) =>
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace mbts {
|
||||
using mbts.user.settings;
|
||||
|
||||
using Spark.Core;
|
||||
using Spark.Core.Math;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -64,7 +63,7 @@ namespace mbts {
|
||||
if (ticks_per_base.is_int()) {
|
||||
return true;
|
||||
}
|
||||
return tick.tick_unit > tick.tick_base && tick.tick_unit_frac().is_int();
|
||||
return tick.tick_unit > tick.tick_base && tick.tick_base == 1;
|
||||
}
|
||||
|
||||
public static bool is_valid(this RegionalTickFactors regional_tick) =>
|
||||
@@ -96,7 +95,7 @@ namespace mbts {
|
||||
|
||||
public static double reduced_base(this TickFactors tick) => MathUtils.factor_out(tick.tick_base, 10);
|
||||
|
||||
public static bool tick_equals(this RegionalTickFactors regional_tick, double x, double y, RoundingMode rounding_mode = RoundingMode.Closest) {
|
||||
public static bool tick_equals(this RegionalTickFactors regional_tick, double x, double y) {
|
||||
if (x.is_nan()) {
|
||||
return y.is_nan();
|
||||
}
|
||||
@@ -108,15 +107,15 @@ namespace mbts {
|
||||
return xtick is not null
|
||||
&& ytick is not null
|
||||
&& xtick.Equals(ytick)
|
||||
&& xtick.tick_equals(x, y, rounding_mode);
|
||||
&& xtick.tick_equals(x, y);
|
||||
}
|
||||
|
||||
public static bool tick_equals(this TickFactors tick, double x, double y, RoundingMode rounding_mode = RoundingMode.Closest) {
|
||||
public static bool tick_equals(this TickFactors tick, double x, double y) {
|
||||
if (x.is_nan()) {
|
||||
return y.is_nan();
|
||||
}
|
||||
var x_units = MathUtils.round_fractional_units(x, tick.tick_unit_frac(), rounding_mode, TickUtils.precision_const<double>());
|
||||
var y_units = MathUtils.round_fractional_units(y, tick.tick_unit_frac(), rounding_mode, TickUtils.precision_const<double>());
|
||||
var x_units = MathUtils.round_fractional_units(x, tick.tick_unit_frac());
|
||||
var y_units = MathUtils.round_fractional_units(y, tick.tick_unit_frac());
|
||||
return x_units == y_units;
|
||||
}
|
||||
|
||||
@@ -138,10 +137,10 @@ namespace mbts {
|
||||
x => NumericCastConverter<T, int>.convert(
|
||||
MathUtils.round_fractional_units(x, tick.tick_unit_frac<T>())));
|
||||
|
||||
public static IEqualityComparer<double> tick_equality_comparer(this TickFactors tick, RoundingMode rounding_mode = RoundingMode.Closest) =>
|
||||
public static IEqualityComparer<double> tick_equality_comparer(this TickFactors tick) =>
|
||||
EqualityComparer.Create<double>(
|
||||
(a, b) => tick_equals(tick, a, b, rounding_mode),
|
||||
x => (int)MathUtils.round_fractional_units(x, tick.tick_unit_frac(), rounding_mode));
|
||||
(a, b) => tick_equals(tick, a, b),
|
||||
x => (int)MathUtils.round_fractional_units(x, tick.tick_unit_frac()));
|
||||
|
||||
public static TickFactors get_tick_for(this RegionalTickFactors rtick, double value) =>
|
||||
value > rtick.low_tick_inclusive_end ? rtick.high_tick : rtick.low_tick;
|
||||
@@ -180,7 +179,10 @@ namespace mbts {
|
||||
if (T.IsInfinity(value)) {
|
||||
return value.ToString();
|
||||
}
|
||||
return _format_tick(tick, value, settings);
|
||||
return tick?.is_valid() ?? false ?
|
||||
_format_tick(tick, value, settings) :
|
||||
_format_no_tick(
|
||||
value, settings.display_multiplier.as_type<double, T>(), settings.show_commas);
|
||||
}
|
||||
|
||||
public static string format<T>(
|
||||
|
||||
@@ -6,7 +6,6 @@ using bts.utils;
|
||||
using mbts.user.settings;
|
||||
|
||||
using Spark.Core;
|
||||
using Spark.Core.Math;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -190,58 +189,52 @@ namespace mbts.spark {
|
||||
public static int compare_tick_fallback_to_double(this ContractSpecificationImmutable contract, double x, double y) =>
|
||||
tick_equals(contract, x, y) ? 0 : x.CompareTo(y);
|
||||
|
||||
public static double round_to_tick(this ContractSpecificationImmutable spec, double value, RoundingMode rounding_mode = RoundingMode.Closest) =>
|
||||
round_to_tick(spec.ticksize(value), value, rounding_mode);
|
||||
public static double round_to_tick(this ContractSpecificationImmutable spec, double value) =>
|
||||
round_to_tick(spec.ticksize(value), value);
|
||||
|
||||
public static decimal round_to_tick(this ContractSpecificationImmutable spec, decimal value, RoundingMode rounding_mode = RoundingMode.Closest) =>
|
||||
round_to_tick((decimal)spec.ticksize((double)value), value, rounding_mode);
|
||||
public static decimal round_to_tick(this ContractSpecificationImmutable spec, decimal value) =>
|
||||
round_to_tick((decimal)spec.ticksize((double)value), value);
|
||||
|
||||
public static double round_to_tick(this RegionalTickFactors rtick, double value, RoundingMode rounding_mode = RoundingMode.Closest) =>
|
||||
round_to_tick(rtick.ticksize(value), value, rounding_mode);
|
||||
public static double round_to_tick(this RegionalTickFactors rtick, double value) =>
|
||||
round_to_tick(rtick.ticksize(value), value);
|
||||
|
||||
public static decimal round_to_tick(this RegionalTickFactors rtick, decimal value, RoundingMode rounding_mode = RoundingMode.Closest) =>
|
||||
round_to_tick((decimal)rtick.ticksize((double)value), value, rounding_mode);
|
||||
public static decimal round_to_tick(this RegionalTickFactors rtick, decimal value) =>
|
||||
round_to_tick((decimal)rtick.ticksize((double)value), value);
|
||||
|
||||
public static double round_to_tick(double tick_size, double value, RoundingMode rounding_mode = RoundingMode.Closest) {
|
||||
double n = rounding_mode switch {
|
||||
RoundingMode.Closest => Math.Round(value / tick_size),
|
||||
RoundingMode.Up => Math.Ceiling((value - _precision_const) / tick_size),
|
||||
RoundingMode.Down => Math.Floor((value + _precision_const) / tick_size),
|
||||
RoundingMode.AbsUp => value < 0
|
||||
? Math.Floor((value + _precision_const) / tick_size)
|
||||
: Math.Ceiling((value - _precision_const) / tick_size),
|
||||
RoundingMode.AbsDown => value < 0
|
||||
? Math.Ceiling((value - _precision_const) / tick_size)
|
||||
: Math.Floor((value + _precision_const) / tick_size),
|
||||
_ => double.NaN,
|
||||
};
|
||||
return n * tick_size;
|
||||
public static double round_to_tick(double tick_size, double value) {
|
||||
double intpart = (long)value;
|
||||
double floatpart = value % 1;
|
||||
floatpart = ((int)Math.Round(floatpart / tick_size)) * tick_size;
|
||||
return intpart + floatpart;
|
||||
}
|
||||
|
||||
public static decimal round_to_tick(decimal tick_size, decimal value, RoundingMode rounding_mode = RoundingMode.Closest) {
|
||||
decimal n = rounding_mode switch {
|
||||
RoundingMode.Closest => Math.Round(value / tick_size),
|
||||
RoundingMode.Up => Math.Ceiling(value / tick_size),
|
||||
RoundingMode.Down => Math.Floor(value / tick_size),
|
||||
RoundingMode.AbsUp => value < 0
|
||||
? Math.Floor(value / tick_size)
|
||||
: Math.Ceiling(value / tick_size),
|
||||
RoundingMode.AbsDown => value < 0
|
||||
? Math.Ceiling(value / tick_size)
|
||||
: Math.Floor(value / tick_size),
|
||||
_ => throw new ArgumentException("Invalid rounding mode", nameof(rounding_mode)),
|
||||
};
|
||||
return n * tick_size;
|
||||
public static decimal round_to_tick(decimal tick_size, decimal value) {
|
||||
decimal intpart = (long)value;
|
||||
decimal floatpart = value % 1;
|
||||
floatpart = ((int)(floatpart / tick_size)) * tick_size;
|
||||
return intpart + floatpart;
|
||||
}
|
||||
|
||||
public static long? count_ticks(this RegionalTickFactors rtick, double value, RoundingMode rounding_mode = RoundingMode.AbsDown) {
|
||||
var rounded_value = rtick.round_to_tick(value, rounding_mode);
|
||||
public static long? count_ticks(this RegionalTickFactors rtick, double value) {
|
||||
if (!rtick.is_valid()) {
|
||||
return null;
|
||||
}
|
||||
var n = _count_ticks(
|
||||
rtick.low_tick.tick_unit_frac(),
|
||||
rtick.low_tick_inclusive_end,
|
||||
rtick.high_tick.tick_unit_frac(),
|
||||
0,
|
||||
rounded_value);
|
||||
value);
|
||||
var orig_n = n;
|
||||
// Enforce proper rounding
|
||||
var rounded_val = rtick.round_to_tick(value);
|
||||
var half_tick = rtick.get_tick_for(value).tick_unit_frac() / 2;
|
||||
if (n < 0 && (value - rounded_val) > half_tick) {
|
||||
++n;
|
||||
} else if (n > 0 && (rounded_val - value) > half_tick) {
|
||||
--n;
|
||||
}
|
||||
Log.assert(n == orig_n);
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -260,19 +253,71 @@ namespace mbts.spark {
|
||||
return tick;
|
||||
}
|
||||
|
||||
public static RegionalTickFactors multiply_tick_size(RegionalTickFactors spec, uint factor) =>
|
||||
new(
|
||||
public static RegionalTickFactors multiply_tick_size(RegionalTickFactors spec, uint factor) {
|
||||
factor = _fit_ticks_per_row_to_dollars(spec, factor) ?? 1;
|
||||
return new(
|
||||
new(spec.low_tick.tick_base, spec.low_tick.tick_unit * factor),
|
||||
new(spec.high_tick.tick_base, spec.high_tick.tick_unit * factor),
|
||||
spec.low_tick_inclusive_end);
|
||||
}
|
||||
|
||||
public static double normalize_price(RegionalTickFactors rtick, double price) {
|
||||
var maybe_ticks = rtick.count_ticks(price);
|
||||
if (!maybe_ticks.HasValue) {
|
||||
if (!price.is_real()) {
|
||||
return price;
|
||||
}
|
||||
var ticks = maybe_ticks.Value;
|
||||
return rtick.tick(0, (long)ticks);
|
||||
var ticks = rtick.count_ticks(price);
|
||||
if (!ticks.HasValue) {
|
||||
return price;
|
||||
}
|
||||
var normalized = rtick.tick(0, ticks.Value);
|
||||
var tick = price > rtick.low_tick_inclusive_end ? rtick.high_tick : rtick.low_tick;
|
||||
// Diff should always be < 1/2 tick
|
||||
Log.assert((normalized - price).abs() < tick.tick_unit_frac() * 0.5);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public static uint fit_ticks_per_row_to_dollars(SecurityImmutable sec, uint ticks_per_row) =>
|
||||
_fit_ticks_per_row_to_dollars(sec, ticks_per_row) ?? 1;
|
||||
|
||||
public static uint fit_ticks_per_row_to_dollars(RegionalTickFactors rtick, uint ticks_per_row) =>
|
||||
_fit_ticks_per_row_to_dollars(rtick, ticks_per_row) ?? 1;
|
||||
|
||||
static uint? _fit_ticks_per_row_to_dollars(SecurityImmutable sec, uint ticks_per_row) =>
|
||||
_fit_ticks_per_row_to_dollars(sec.regional_ticks(), ticks_per_row);
|
||||
|
||||
static uint? _fit_ticks_per_row_to_dollars(RegionalTickFactors tf, uint ticks_per_row) {
|
||||
if (ticks_per_row == 0)
|
||||
return null;
|
||||
// it is assumed that low tick will be the same as high tick;
|
||||
// however, best to double check - return null if this isn't the case (let the caller decide what to do).
|
||||
if (!tf.low_tick.Equals(tf.high_tick) || !tf.is_valid() || tf.low_tick.is_integer())
|
||||
return null;
|
||||
var ticks_per_dollar = (uint)(tf.low_tick.tick_base / tf.low_tick.tick_unit);
|
||||
// if ticks_per_row is greater than ticks_per_dollar, round to a multiple of ticks_per_dollar
|
||||
if (ticks_per_row > ticks_per_dollar) {
|
||||
if (ticks_per_dollar == 0) {
|
||||
return null;
|
||||
}
|
||||
return (uint)(ticks_per_dollar * Math.Floor((double)(ticks_per_row / ticks_per_dollar)));
|
||||
} else if (ticks_per_row == ticks_per_dollar) {
|
||||
return ticks_per_row;
|
||||
} else {
|
||||
uint max_decrease_adj = ticks_per_row;
|
||||
uint max_increase_adj = ticks_per_dollar - ticks_per_row - 1;
|
||||
for (uint adj = 0; adj < Math.Max(max_decrease_adj, max_increase_adj); adj++) {
|
||||
if (adj < max_decrease_adj) {
|
||||
var result = ticks_per_row - adj;
|
||||
if (ticks_per_dollar % result == 0)
|
||||
return result;
|
||||
}
|
||||
if (adj < max_increase_adj) {
|
||||
var result = ticks_per_row + adj;
|
||||
if (ticks_per_dollar % result == 0)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static long? _count_ticks(
|
||||
@@ -322,20 +367,8 @@ namespace mbts.spark {
|
||||
|
||||
static long count_single_tick_sz(
|
||||
double tick_sz, double from, double to) {
|
||||
from = round_to_tick(tick_sz, from, RoundingMode.AbsDown);
|
||||
to = round_to_tick(tick_sz, to, RoundingMode.AbsDown);
|
||||
if (from < 0) {
|
||||
from -= _precision_const;
|
||||
} else {
|
||||
from += _precision_const;
|
||||
}
|
||||
if (to < 0) {
|
||||
to -= _precision_const;
|
||||
} else {
|
||||
to += _precision_const;
|
||||
}
|
||||
var from_n = (long)(from / tick_sz);
|
||||
var to_n = (long)(to / tick_sz);
|
||||
var from_n = (long)MathUtils.round_fractional_units(from, tick_sz);
|
||||
var to_n = (long)MathUtils.round_fractional_units(to, tick_sz);
|
||||
return to_n - from_n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
|
||||
static IEnumerable<ContractLegImmutable> _get_root_legs(
|
||||
ContractLegImmutable leg, double root_quantity) {
|
||||
_validate_root_quantity(root_quantity);
|
||||
if (leg.HasSecurity) {
|
||||
return _get_root_legs(leg.Security, leg.signed_quantity() * root_quantity);
|
||||
}
|
||||
@@ -53,8 +54,10 @@
|
||||
return new[] { new ContractLegImmutable(mleg) };
|
||||
}
|
||||
|
||||
static IEnumerable<ContractLegImmutable> _get_root_legs(SecurityImmutable security, double root_quantity) =>
|
||||
security.ContractCase switch {
|
||||
static IEnumerable<ContractLegImmutable> _get_root_legs(
|
||||
SecurityImmutable security, double root_quantity) {
|
||||
_validate_root_quantity(root_quantity);
|
||||
return security.ContractCase switch {
|
||||
Security.ContractOneofCase.Spread => security.Spread.Legs.SelectMany(l => _get_root_legs(l.Security, root_quantity * l.signed_quantity())).ToArray(),
|
||||
Security.ContractOneofCase.Synthetic => security.Synthetic.Legs.SelectMany(l => _get_root_legs(l.Security, root_quantity * l.signed_quantity())).ToArray(),
|
||||
_ => new[] {
|
||||
@@ -66,5 +69,13 @@
|
||||
})
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static void _validate_root_quantity(double root_quantity) {
|
||||
if (!root_quantity.is_real_non_zero()) {
|
||||
throw new ArgumentException(
|
||||
$"Expected real non-zero value (actually {root_quantity})", nameof(root_quantity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -640,9 +640,6 @@ namespace System.Collections.Generic {
|
||||
public static List<T> ToListSorted<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> key_selector, IComparer<TKey> key_comparer) =>
|
||||
enumerable.ToListSorted(key_comparer.wrap(key_selector));
|
||||
|
||||
public static List<T> ToListSorted<T>(this IEnumerable<T> enumerable) where T : IComparable<T> =>
|
||||
enumerable.ToListSorted(Comparer<T>.Default);
|
||||
|
||||
public static List<T> ToListSorted<T>(this IEnumerable<T> enumerable, IComparer<T> comparer) {
|
||||
var list = new List<T>(enumerable);
|
||||
list.Sort(comparer);
|
||||
|
||||
+1
-2
@@ -73,8 +73,7 @@ namespace bts.collections {
|
||||
public IEnumerable<TRow> all_rows => _tree.all_rows;
|
||||
public int Count => _rows.Count;
|
||||
public int total_row_count => _tree.total_row_count;
|
||||
public IEnumerable<TRow> GetAllRows(bool excludeFiltered = false) =>
|
||||
_tree.GetAllRows(excludeFiltered);
|
||||
|
||||
public IDisposable defer_refresh() =>
|
||||
_refresh.defer();
|
||||
|
||||
|
||||
-2
@@ -61,8 +61,6 @@ namespace bts.collections {
|
||||
IEnumerable<INode<TRow>> _root_nodes =>
|
||||
_filter == null ? _tree.RootNodes : _cached_filtered ?? (_cached_filtered = _tree.RootNodes.filtered(_filter).sorted_hard(_comparer));
|
||||
|
||||
public IEnumerable<TRow> GetAllRows(bool excludeFiltered = false) =>
|
||||
excludeFiltered ? all_rows : _tree;
|
||||
// collapse
|
||||
public void collapse_row(TKey key) => _toggle_is_expanded(key, false);
|
||||
public void collapse_all() => _toggle_is_expanded(false);
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Spark.Core.Math;
|
||||
|
||||
public enum RoundingMode {
|
||||
None,
|
||||
// Round to the closest integer (up if decimal part is >= 0.5, otherwise down)
|
||||
Closest,
|
||||
// Round up
|
||||
Up,
|
||||
// Round down
|
||||
Down,
|
||||
// Round positive numbers up, and negative numbers down
|
||||
AbsUp,
|
||||
// Round positive numbers down, and negative numbers up
|
||||
AbsDown
|
||||
}
|
||||
@@ -1,163 +1,143 @@
|
||||
namespace System {
|
||||
using bts;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
public static class ConvertUtils {
|
||||
public static object try_convert(Type type, object obj) {
|
||||
try_convert(type, obj, out object result);
|
||||
return result;
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
public static bool try_convert<T>(
|
||||
object obj,
|
||||
[NotNullWhen(true)]out T? result,
|
||||
LogLevel? log_level = null,
|
||||
LogSubsystem log_subsystem = LogSubsystem.gen,
|
||||
string? name = null) {
|
||||
if (obj is T t) {
|
||||
result = t;
|
||||
return true;
|
||||
}
|
||||
if (try_convert(typeof(T), obj, out var result_obj)) {
|
||||
result = (T)result_obj;
|
||||
return true;
|
||||
}
|
||||
if (log_level.HasValue) {
|
||||
name = name is null ? "obj" : $"'{name}'";
|
||||
var msg = $"Expected {name} to be of type '{typeof(T)}', actually " +
|
||||
$"'{obj?.GetType()?.ToString() ?? "null"}'.";
|
||||
Log.log(new(log_subsystem, log_level.Value, msg));
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
#nullable disable
|
||||
|
||||
public static bool try_convert(Type type, object obj, out object result) {
|
||||
if (obj == null) {
|
||||
result = get_default(type);
|
||||
return false;
|
||||
} else if (type.IsPrimitive && obj is IConvertible) {
|
||||
result = Convert.ChangeType(obj, type);
|
||||
return true;
|
||||
} else if (type.IsAssignableFrom(obj.GetType())) {
|
||||
result = obj;
|
||||
return true;
|
||||
} else if (type.IsEnum && obj is string) {
|
||||
result = try_parse_enum(type, obj.ToString());
|
||||
return true;
|
||||
} else if (type.IsNullable()) {
|
||||
if (try_convert(type.GetGenericArguments()[0], obj, out result)) {
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
} else {
|
||||
result = get_default(type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static T try_convert<T>(object obj) =>
|
||||
(T)try_convert(typeof(T), obj);
|
||||
|
||||
public static T try_convert<T>(object obj, T default_value) =>
|
||||
try_convert(obj, out T result) ? result : default_value;
|
||||
|
||||
public static bool try_convert<T>(object obj, out T result) {
|
||||
bool rv = try_convert(typeof(T), obj, out object r_obj);
|
||||
result = (T)r_obj;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static object get_default(Type type) =>
|
||||
type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
|
||||
public static T ValueOrDefault<T>(T? nullable) where T : struct => nullable.HasValue ? nullable.Value : default;
|
||||
|
||||
public static bool is_compatible<T>(object obj) { return obj is T || (obj == null && default(T) == null); }
|
||||
|
||||
public static bool try_convert_args<T1, T2>(object[] args, out T1 arg1, out T2 arg2) {
|
||||
if (args == null || args.Length != 2) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool try_convert_args<T1, T2, T3>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3) {
|
||||
if (args == null || args.Length != 3) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
arg3 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
rv |= try_convert(args[2], out arg3);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool try_convert_args<T1, T2, T3, T4>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4) {
|
||||
if (args == null || args.Length != 4) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
arg3 = default;
|
||||
arg4 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
rv |= try_convert(args[2], out arg3);
|
||||
rv |= try_convert(args[3], out arg4);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool TryConvertArgs<T1, T2, T3, T4, T5>(IEnumerable<object> args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4, out T5 arg5) {
|
||||
using var en = args.GetEnumerator();
|
||||
bool success = true;
|
||||
convertNext(en, ref success, out arg1);
|
||||
convertNext(en, ref success, out arg2);
|
||||
convertNext(en, ref success, out arg3);
|
||||
convertNext(en, ref success, out arg4);
|
||||
convertNext(en, ref success, out arg5);
|
||||
return success;
|
||||
}
|
||||
|
||||
public static object try_parse_enum(Type enum_type, string value, bool ignore_case = true) {
|
||||
if (!enum_type.IsEnum) throw new ArgumentException("Must an enum type.", nameof(enum_type));
|
||||
Array vals = Enum.GetValues(enum_type);
|
||||
var str_comp = ignore_case ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
|
||||
foreach (object v in vals) {
|
||||
if (string.Equals(v.ToString(), value, str_comp))
|
||||
return v;
|
||||
}
|
||||
return Activator.CreateInstance(enum_type);
|
||||
}
|
||||
|
||||
public static bool update_value_type<T>(ref T field, T newval) where T : struct, IEquatable<T> {
|
||||
bool rv = !field.Equals(newval);
|
||||
field = newval;
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void convertNext<T>(IEnumerator<object> enumerator, ref bool success, out T arg) {
|
||||
if (!success || !enumerator.MoveNext()) {
|
||||
arg = default;
|
||||
success = false;
|
||||
}
|
||||
success = try_convert(enumerator.Current, out arg);
|
||||
}
|
||||
|
||||
public static class CastDelegate<T, TResult> {
|
||||
public static readonly Func<T, TResult> value =
|
||||
v => v is TResult r ? r : throw new InvalidCastException($"Failed to cast element in source collection from type {typeof(T)} to {typeof(TResult)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace System {
|
||||
using bts;
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
public static class ConvertUtils {
|
||||
public static object try_convert(Type type, object obj) {
|
||||
try_convert(type, obj, out object result);
|
||||
return result;
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
public static bool try_convert<T>(
|
||||
object obj,
|
||||
[NotNullWhen(true)]out T? result,
|
||||
LogLevel? log_level = null,
|
||||
LogSubsystem log_subsystem = LogSubsystem.gen,
|
||||
string? name = null) {
|
||||
if (obj is T t) {
|
||||
result = t;
|
||||
return true;
|
||||
}
|
||||
if (try_convert(typeof(T), obj, out var result_obj)) {
|
||||
result = (T)result_obj;
|
||||
return true;
|
||||
}
|
||||
if (log_level.HasValue) {
|
||||
name = name is null ? "obj" : $"'{name}'";
|
||||
var msg = $"Expected {name} to be of type '{typeof(T)}', actually " +
|
||||
$"'{obj?.GetType()?.ToString() ?? "null"}'.";
|
||||
Log.log(new(log_subsystem, log_level.Value, msg));
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
#nullable disable
|
||||
|
||||
public static bool try_convert(Type type, object obj, out object result) {
|
||||
if (obj == null) {
|
||||
result = get_default(type);
|
||||
return false;
|
||||
} else if (type.IsPrimitive && obj is IConvertible) {
|
||||
result = Convert.ChangeType(obj, type);
|
||||
return true;
|
||||
} else if (type.IsAssignableFrom(obj.GetType())) {
|
||||
result = obj;
|
||||
return true;
|
||||
} else if (type.IsEnum && obj is string) {
|
||||
result = try_parse_enum(type, obj.ToString());
|
||||
return true;
|
||||
} else if (type.IsNullable()) {
|
||||
if (try_convert(type.GetGenericArguments()[0], obj, out result)) {
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
} else {
|
||||
result = get_default(type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static T try_convert<T>(object obj) =>
|
||||
(T)try_convert(typeof(T), obj);
|
||||
|
||||
public static T try_convert<T>(object obj, T default_value) =>
|
||||
try_convert(obj, out T result) ? result : default_value;
|
||||
|
||||
public static bool try_convert<T>(object obj, out T result) {
|
||||
bool rv = try_convert(typeof(T), obj, out object r_obj);
|
||||
result = (T)r_obj;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static object get_default(Type type) =>
|
||||
type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
|
||||
public static T ValueOrDefault<T>(T? nullable) where T : struct => nullable.HasValue ? nullable.Value : default;
|
||||
|
||||
public static bool is_compatible<T>(object obj) { return obj is T || (obj == null && default(T) == null); }
|
||||
|
||||
public static bool try_convert_args<T1, T2>(object[] args, out T1 arg1, out T2 arg2) {
|
||||
if (args == null || args.Length != 2) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool try_convert_args<T1, T2, T3>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3) {
|
||||
if (args == null || args.Length != 3) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
arg3 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
rv |= try_convert(args[2], out arg3);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool try_convert_args<T1, T2, T3, T4>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4) {
|
||||
if (args == null || args.Length != 4) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
arg3 = default;
|
||||
arg4 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
rv |= try_convert(args[2], out arg3);
|
||||
rv |= try_convert(args[3], out arg4);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static object try_parse_enum(Type enum_type, string value, bool ignore_case = true) {
|
||||
if (!enum_type.IsEnum) throw new ArgumentException("Must an enum type.", nameof(enum_type));
|
||||
Array vals = Enum.GetValues(enum_type);
|
||||
var str_comp = ignore_case ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
|
||||
foreach (object v in vals) {
|
||||
if (string.Equals(v.ToString(), value, str_comp))
|
||||
return v;
|
||||
}
|
||||
return Activator.CreateInstance(enum_type);
|
||||
}
|
||||
|
||||
public static bool update_value_type<T>(ref T field, T newval) where T : struct, IEquatable<T> {
|
||||
bool rv = !field.Equals(newval);
|
||||
field = newval;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static class CastDelegate<T, TResult> {
|
||||
public static readonly Func<T, TResult> value =
|
||||
v => v is TResult r ? r : throw new InvalidCastException($"Failed to cast element in source collection from type {typeof(T)} to {typeof(TResult)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
@@ -10,9 +10,8 @@ namespace bts
|
||||
static int _needs_init_spark_lib = 0;
|
||||
|
||||
public static void init(string config_source = null) {
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
Console.WriteLine(
|
||||
$"Configuring Spark {version} with {(config_source is null ? "defaults" : $"'{config_source}'")}");
|
||||
$"Configuring BTS with {(config_source is null ? "defaults" : $"'{config_source}'")}");
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
utils.BTSNativeMethods.provide_command_line(args, args.Length);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
namespace bts.utils {
|
||||
using Spark.Core;
|
||||
using Spark.Core.Math;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -195,22 +194,6 @@ namespace bts.utils {
|
||||
public static T round_fractional_units<T>(T value, T fractional_unit) where T : IFloatingPoint<T> =>
|
||||
T.Round(value / fractional_unit);
|
||||
|
||||
public static T round_fractional_units<T>(T value, T fractional_unit, RoundingMode rounding_mode) where T : struct, IFloatingPoint<T> =>
|
||||
round_fractional_units(value, fractional_unit, rounding_mode, T.Zero);
|
||||
|
||||
public static T round_fractional_units<T>(T value, T fractional_unit, RoundingMode rounding_mode, T epsilon) where T : IFloatingPoint<T> =>
|
||||
rounding_mode switch {
|
||||
RoundingMode.Up => T.Ceiling((value - epsilon) / fractional_unit),
|
||||
RoundingMode.Down => T.Floor((value + epsilon) / fractional_unit),
|
||||
RoundingMode.AbsUp => value < T.Zero
|
||||
? T.Floor((value + epsilon) / fractional_unit)
|
||||
: T.Ceiling((value - epsilon) / fractional_unit),
|
||||
RoundingMode.AbsDown => value < T.Zero
|
||||
? T.Ceiling((value - epsilon) / fractional_unit)
|
||||
: T.Floor((value + epsilon) / fractional_unit),
|
||||
_ => T.Round(value / fractional_unit),
|
||||
};
|
||||
|
||||
public static double round_down_abs(double value) =>
|
||||
round_down_abs(value, EPSILON);
|
||||
|
||||
@@ -490,12 +473,10 @@ namespace bts.utils {
|
||||
}
|
||||
|
||||
static bool try_mk_floating_point_type([NotNullWhen(true)] out Type type) {
|
||||
if (typeof(T).GetInterfaces().Any(iface => iface.IsGenericType && iface.GetGenericTypeDefinition().Equals(typeof(IFloatingPoint<>)))) {
|
||||
try {
|
||||
type = typeof(IFloatingPoint<>).MakeGenericType(typeof(T));
|
||||
return type.IsAssignableFrom(typeof(T));
|
||||
} catch { }
|
||||
}
|
||||
try {
|
||||
type = typeof(IFloatingPoint<>).MakeGenericType(typeof(T));
|
||||
return type.IsAssignableFrom(typeof(T));
|
||||
} catch { }
|
||||
type = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -65,10 +65,10 @@ namespace bts.utils {
|
||||
public static bool epsilon_equals<T>(this T x, T y, T epsilon) where T : IFloatingPoint<T> =>
|
||||
MathUtils.epsilon_equals(x, y, epsilon);
|
||||
|
||||
public static bool MarginalEquals<T>(this T x, T y) where T : IFloatingPoint<T> =>
|
||||
MarginalEquals(x, y, MathUtils.epsilon<T>());
|
||||
public static bool marginal_equals<T>(T x, T y) where T : IFloatingPoint<T> =>
|
||||
marginal_equals(x, y, MathUtils.epsilon<T>());
|
||||
|
||||
public static bool MarginalEquals<T>(this T x, T y, T margin) where T : IFloatingPoint<T> =>
|
||||
public static bool marginal_equals<T>(T x, T y, T margin) where T : IFloatingPoint<T> =>
|
||||
MathUtils.marginal_equals(x, y, margin);
|
||||
|
||||
public static bool is_within_range<T>(
|
||||
|
||||
@@ -6,8 +6,6 @@ using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.user.settings;
|
||||
|
||||
using Spark.Core.Math;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -161,7 +159,7 @@ namespace bts.tests.security {
|
||||
var price_break = tick.low_tick_inclusive_end;
|
||||
foreach (var p in prices) {
|
||||
var expected_ticks_from_zero = TickTestHelper.count_ticks(tick, p);
|
||||
var actual_ticks_from_zero = TickUtils.count_ticks(tick, p, RoundingMode.Closest);
|
||||
var actual_ticks_from_zero = TickUtils.count_ticks(tick, p);
|
||||
Assert.That(expected_ticks_from_zero == actual_ticks_from_zero,
|
||||
$"Expected ticks from zero to be '{expected_ticks_from_zero}', actually '{actual_ticks_from_zero}' " +
|
||||
$"(low/high/pricebreak: {tick.low_tick}/{tick.high_tick}/{price_break}, price: {p}).");
|
||||
@@ -179,6 +177,24 @@ namespace bts.tests.security {
|
||||
}
|
||||
}
|
||||
|
||||
void _test_tick_increment(ContractSpecificationImmutable spec, double from, double to) {
|
||||
if (!from.is_real() || !to.is_real())
|
||||
return;
|
||||
var (min, max) = MathUtils.get_outliers(from, to);
|
||||
if (spec.PriceBreak.is_real()) {
|
||||
if (min >= spec.PriceBreak) {
|
||||
//_test_single_tick(spec.HighTick, min, max);
|
||||
} else if (max <= spec.PriceBreak) {
|
||||
_test_tick_increment(spec, spec.LowTick, min, max);
|
||||
} else {
|
||||
//if (round_ticks(spec.LowTick, min, out var ))
|
||||
_test_tick_increment_across_break(spec, min, max);
|
||||
}
|
||||
} else {
|
||||
_test_tick_increment(spec, spec.LowTick, min, max);
|
||||
}
|
||||
}
|
||||
|
||||
void _test_tick_increment(RegionalTickFactors tick, double from, double to) {
|
||||
if (!from.is_real() || !to.is_real())
|
||||
return;
|
||||
@@ -244,6 +260,18 @@ namespace bts.tests.security {
|
||||
Assert.That(MathUtils.marginal_equals(batch_tick_down_result, rounded_min, TickUtils.precision_const<double>()));
|
||||
}
|
||||
|
||||
void _test_tick_increment(ContractSpecificationImmutable spec, double tick_size, double min, double max) {
|
||||
// min round/partial tick
|
||||
Assert.That(TickTestHelper.round_ticks(tick_size, min, out var rounded_min, out var rounded_min_ticks));
|
||||
var min_partial_tick = TickTestHelper.check_partial_tick(spec, min, rounded_min, true);
|
||||
// max round/partial tick
|
||||
Assert.That(TickTestHelper.round_ticks(tick_size, max, out var rounded_max, out var rounded_max_ticks));
|
||||
var max_partial_tick = TickTestHelper.check_partial_tick(spec, max, rounded_max, false);
|
||||
var full_ticks = rounded_max_ticks - rounded_min_ticks;
|
||||
Assert.That(MathUtils.marginal_equals(rounded_max, spec.tickup(min, full_ticks + min_partial_tick), TickUtils.precision_const<double>()));
|
||||
Assert.That(MathUtils.marginal_equals(rounded_min, spec.tickup(max, -(full_ticks + max_partial_tick)), TickUtils.precision_const<double>()));
|
||||
}
|
||||
|
||||
void _test_tick_increment(RegionalTickFactors tick, double tick_size, double min, double max) {
|
||||
// min round/partial tick
|
||||
Assert.That(TickTestHelper.round_ticks(tick_size, min, out var rounded_min, out var rounded_min_ticks));
|
||||
|
||||
@@ -13,6 +13,6 @@ message DatabentoMarketDataConfig {
|
||||
double replay_sleep_timer_sec = 50;
|
||||
string key = 60;
|
||||
string dataset = 70;
|
||||
uint32 filter_securities_upto_n_months= 80;
|
||||
bool filter_securities_one_year= 80;
|
||||
bool load_reference_data = 90;
|
||||
}
|
||||
@@ -13,6 +13,4 @@ option cc_enable_arenas = true;
|
||||
message MarketDataReplayConfig {
|
||||
bts.api.Venue venue = 10;
|
||||
double replay_speed = 20;
|
||||
// whether to replay books
|
||||
bool publish_boostrap_books_only = 30;
|
||||
}
|
||||
@@ -774,14 +774,8 @@ bts::api::orderman::RoutingRecord AdminServiceUtils::get_routing_record(
|
||||
out_result.set_is_success(true);
|
||||
return rv;
|
||||
}
|
||||
case api::VENUE_BINANCE: {
|
||||
rv.set_session("binance_1");
|
||||
rv.set_account(account_ptr->name());
|
||||
rv.set_account(user_ptr->name());
|
||||
out_result.set_is_success(true);
|
||||
return rv;
|
||||
}
|
||||
case api::VENUE_FTX: // TODO
|
||||
case api::VENUE_BINANCE: // TODO
|
||||
case api::VENUE_BINANCE_COIN_FUTURES:
|
||||
case api::VENUE_BINANCE_US:
|
||||
case api::Venue::VENUE_COINBASE:
|
||||
|
||||
@@ -4,11 +4,16 @@
|
||||
#include "lib/api/services/OrderService.server.pb.h"
|
||||
#include "lib/config/config.h"
|
||||
#include "lib/config/protos/toggles.pb.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include <memory>
|
||||
|
||||
namespace bts {
|
||||
namespace admin {
|
||||
|
||||
using namespace bts::api::admin;
|
||||
using namespace bts::api::orderman;
|
||||
|
||||
class AdminServiceUtils {
|
||||
public:
|
||||
struct ExchangeSubAccountResolver {
|
||||
@@ -28,7 +33,7 @@ class AdminServiceUtils {
|
||||
return is_int_account ? entry.bts_account_name()
|
||||
: entry.exchange_subaccount();
|
||||
};
|
||||
if (sec == nullptr)
|
||||
if(sec == nullptr)
|
||||
return nullptr;
|
||||
for (const auto &u : exch_sub_accounts.accounts()) {
|
||||
if (get_account(u) == account)
|
||||
@@ -54,247 +59,224 @@ class AdminServiceUtils {
|
||||
////////////////////////////////////////////
|
||||
// finds the max id out of all the existing connections
|
||||
// (todo: deleted connections)
|
||||
static uint32_t find_max_connection_id(const api::admin::AdminData &data);
|
||||
static uint32_t find_max_connection_id(const AdminData &data);
|
||||
// finds a connection by name and returns a mutable pointer to it
|
||||
static const api::admin::ExchangeConnection *find_connection(
|
||||
const api::admin::AdminData &data, const std::string &connection_name);
|
||||
static const ExchangeConnection *find_connection(
|
||||
const AdminData &data, const std::string &connection_name);
|
||||
// finds the presence of an exchange in a connection
|
||||
static bool has_exchange(
|
||||
const api::admin::ExchangeConnection &conn, api::Venue exchange);
|
||||
const ExchangeConnection &conn, bts::api::Venue exchange);
|
||||
// finds the default connections for an exchange
|
||||
// in a given account
|
||||
static const api::admin::ExchangeConnection *find_default_account_connection(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
api::Venue exchange);
|
||||
static const api::admin::ExchangeConnection *resolve_cme_connection(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
static const ExchangeConnection *find_default_account_connection(
|
||||
const AdminData &data,
|
||||
const BTSAccount &account,
|
||||
bts::api::Venue exchange);
|
||||
static const ExchangeConnection *resolve_cme_connection(
|
||||
const AdminData &data,
|
||||
const BTSAccount &account,
|
||||
const api::Security &sec);
|
||||
static const api::admin::ExchangeConnection *resolve_ice_connection(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
static const ExchangeConnection *resolve_ice_connection(
|
||||
const AdminData &data,
|
||||
const BTSAccount &account,
|
||||
const api::Security &sec);
|
||||
static const api::admin::ExchangeConnection *resolve_sim_connection(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
static const ExchangeConnection *resolve_sim_connection(
|
||||
const AdminData &data,
|
||||
const BTSAccount &account,
|
||||
const api::Security &sec);
|
||||
static const api::admin::ExchangeConnection *resolve_connection(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
const api::Security &sec);
|
||||
|
||||
static const ExchangeConnection *resolve_connection(
|
||||
const AdminData &data,
|
||||
const BTSAccount &account,
|
||||
const bts::api::Security &sec);
|
||||
////////////////////////////////////////////
|
||||
// connection mutators /////////////////////
|
||||
////////////////////////////////////////////
|
||||
// adds a new connection (does not check for name uniqueness)
|
||||
static api::admin::ExchangeConnection *add_connection(
|
||||
api::admin::AdminData &data,
|
||||
const api::admin::ExchangeConnection &new_connection);
|
||||
static ExchangeConnection *add_connection(
|
||||
AdminData &data, const ExchangeConnection &new_connection);
|
||||
// reads the connections from config and fills in the
|
||||
// admin data (with names only)
|
||||
static void fill_connections_from_config(api::admin::AdminData &admin_data);
|
||||
|
||||
static void fill_connections_from_config(AdminData &admin_data);
|
||||
////////////////////////////////////////////
|
||||
// account accessors ///////////////////////
|
||||
////////////////////////////////////////////
|
||||
// finds the account from the name and returns
|
||||
// a mutable pointer to the account
|
||||
static api::admin::BTSAccount *find_account(
|
||||
api::admin::AdminData &data,
|
||||
static BTSAccount *find_account(
|
||||
AdminData &data,
|
||||
const std::string &acct_name,
|
||||
bool include_deleted = false);
|
||||
static std::map<std::string, uint32_t> get_clearing_account_map(
|
||||
const api::admin::AdminData &);
|
||||
const AdminData &);
|
||||
|
||||
// finds the account from a clearing account (and its venue)
|
||||
static api::admin::BTSAccount const *find_account(
|
||||
const api::admin::AdminData &data,
|
||||
static BTSAccount const *find_account(
|
||||
const AdminData &data,
|
||||
const std::string &clearing_account,
|
||||
api::Venue);
|
||||
bts::api::Venue);
|
||||
// same as above for const pointer
|
||||
static const api::admin::BTSAccount *find_account(
|
||||
const api::admin::AdminData &data,
|
||||
static const BTSAccount *find_account(
|
||||
const AdminData &data,
|
||||
const std::string &acct_name,
|
||||
bool include_deleted = false);
|
||||
// finds the account from the id and returns
|
||||
// a mutable pointer to the account
|
||||
static api::admin::BTSAccount *find_account(
|
||||
api::admin::AdminData &data, uint32_t id, bool include_deleted = false);
|
||||
static BTSAccount *find_account(
|
||||
AdminData &data, uint32_t id, bool include_deleted = false);
|
||||
// like above but returns a const pointer
|
||||
static const api::admin::BTSAccount *find_account(
|
||||
const api::admin::AdminData &data,
|
||||
uint32_t id,
|
||||
bool include_deleted = false);
|
||||
static const BTSAccount *find_account(
|
||||
const AdminData &data, uint32_t id, bool include_deleted = false);
|
||||
// this function is an indirection layer for account access
|
||||
// so deletions are dealt in a more deliberate manner
|
||||
static std::vector<const api::admin::BTSAccount *> accounts(
|
||||
const api::admin::AdminData &data, bool include_deleted);
|
||||
static std::vector<api::admin::BTSAccount *> accounts_mutable(
|
||||
api::admin::AdminData &data, bool include_deleted);
|
||||
static std::vector<const BTSAccount *> accounts(
|
||||
const AdminData &data, bool include_deleted);
|
||||
static std::vector<BTSAccount *> accounts_mutable(
|
||||
AdminData &data, bool include_deleted);
|
||||
// finds the max id amongts all the existing accounts
|
||||
static uint32_t find_max_acct_id(const api::admin::AdminData &data);
|
||||
static uint32_t find_max_acct_id(const AdminData &data);
|
||||
// gets the account data for cme (and goes up the hierarchy
|
||||
// if needed)
|
||||
static const api::admin::CMEAccountData *get_account_cme_data(
|
||||
const api::admin::BTSAccount &account,
|
||||
const api::admin::AdminData &data);
|
||||
static const api::admin::ICEAccountData *get_account_ice_data(
|
||||
const api::admin::BTSAccount &account,
|
||||
const api::admin::AdminData &data);
|
||||
static const api::admin::SIMAccountData *get_account_sim_data(
|
||||
const api::admin::BTSAccount &account,
|
||||
const api::admin::AdminData &data);
|
||||
static const CMEAccountData *get_account_cme_data(
|
||||
const BTSAccount &account, const AdminData &data);
|
||||
static const ICEAccountData *get_account_ice_data(
|
||||
const BTSAccount &account, const AdminData &data);
|
||||
static const SIMAccountData *get_account_sim_data(
|
||||
const BTSAccount &account, const AdminData &data);
|
||||
// returns true if an account has a connection
|
||||
bool static account_has_connection(
|
||||
const api::admin::BTSAccount &account,
|
||||
const std::string &connection_name);
|
||||
const BTSAccount &account, const std::string &connection_name);
|
||||
// finds the external/clearing account associated
|
||||
// with an account
|
||||
static std::string find_account_exchange_account(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
const AdminData &data,
|
||||
const BTSAccount &account,
|
||||
api::Venue exchange,
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
AdminServiceOperationResult &out_result);
|
||||
// returns a map of the accounts vs their 'is_active'
|
||||
static std::map<uint32_t, bool> accounts_map(
|
||||
const api::admin::AdminData &admin_data);
|
||||
static std::map<uint32_t, bool> accounts_map(const AdminData &admin_data);
|
||||
// validates that the update to the account is valid
|
||||
static void validate_account_update(
|
||||
const api::admin::AdminData &admin_data,
|
||||
const api::admin::BTSAccount &updated_account,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
const AdminData &admin_data,
|
||||
const BTSAccount &updated_account,
|
||||
AdminServiceOperationResult &result);
|
||||
// validates that the account exchange changes are valid
|
||||
// (ex > 12 characters for accounts at CME)
|
||||
static void validate_account_exchange_data(
|
||||
const api::admin::BTSAccount &account,
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
static api::orderman::RoutingRecord get_routing_record(
|
||||
const api::admin::AdminData &data,
|
||||
const BTSAccount &account, AdminServiceOperationResult &out_result);
|
||||
static RoutingRecord get_routing_record(
|
||||
const AdminData &data,
|
||||
const std::string &account_name,
|
||||
const std::string &user_name,
|
||||
const api::Security &sec,
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
static api::orderman::RoutingRecord get_routing_record(
|
||||
const api::admin::AdminData &data,
|
||||
const bts::api::Security &sec,
|
||||
AdminServiceOperationResult &out_result);
|
||||
static RoutingRecord get_routing_record(
|
||||
const AdminData &data,
|
||||
uint32_t account_id,
|
||||
uint32_t user_id,
|
||||
const api::Security &sec,
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
|
||||
const bts::api::Security &sec,
|
||||
AdminServiceOperationResult &out_result);
|
||||
////////////////////////////////////////////
|
||||
// account mutators ////////////////////////
|
||||
////////////////////////////////////////////
|
||||
static api::admin::BTSAccount *create_account(
|
||||
api::admin::AdminData &data,
|
||||
static BTSAccount *create_account(
|
||||
AdminData &data,
|
||||
uint32_t parent_account_id,
|
||||
const std::string &account_name,
|
||||
const std::string &description,
|
||||
bool is_active,
|
||||
api::admin::AdminServiceCreateAccountResult &result);
|
||||
AdminServiceCreateAccountResult &result);
|
||||
// writes over the account data completely
|
||||
static void update_account(
|
||||
api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &updated_account,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
AdminData &data,
|
||||
const BTSAccount &updated_account,
|
||||
AdminServiceOperationResult &result);
|
||||
static void delete_account(
|
||||
api::admin::AdminData &admin_data,
|
||||
const api::admin::IDAPIArgument &id,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
AdminData &admin_data,
|
||||
const IDAPIArgument &id,
|
||||
AdminServiceOperationResult &result);
|
||||
static void set_account_exchange_data(
|
||||
api::admin::BTSAccount *account,
|
||||
const api::admin::ExchangeAccountData &exchange_data);
|
||||
BTSAccount *account, const ExchangeAccountData &exchange_data);
|
||||
// this is not a generally useful function but since
|
||||
// connection are in config, this cleans up the account's
|
||||
// connections and syncs it with the list in api::admin::AdminData
|
||||
// connections and syncs it with the list in AdminData
|
||||
static void sync_account_connections(
|
||||
const api::admin::AdminData &data, api::admin::BTSAccount &account);
|
||||
|
||||
const AdminData &data, BTSAccount &account);
|
||||
////////////////////////////////////////////
|
||||
// group accessors /////////////////////////
|
||||
////////////////////////////////////////////
|
||||
// given an id, finds the mutable pointer to a group
|
||||
static api::admin::BTSUserGroup *find_group(
|
||||
api::admin::AdminData &data, uint32_t id);
|
||||
static BTSUserGroup *find_group(AdminData &data, uint32_t id);
|
||||
// given an id, finds the const pointer to a group
|
||||
static const api::admin::BTSUserGroup *const find_group(
|
||||
const api::admin::AdminData &data, uint32_t id);
|
||||
static const BTSUserGroup *const find_group(
|
||||
const AdminData &data, uint32_t id);
|
||||
// given a name, finds the mutable pointer to a group
|
||||
static api::admin::BTSUserGroup *find_group(
|
||||
api::admin::AdminData &data, const std::string &group_name);
|
||||
static BTSUserGroup *find_group(
|
||||
AdminData &data, const std::string &group_name);
|
||||
// finds the max id out of all the groups
|
||||
static uint32_t find_max_group_id(const api::admin::AdminData &data);
|
||||
static uint32_t find_max_group_id(const AdminData &data);
|
||||
// travels up the group tree until it finds a populated cme data
|
||||
static const api::admin::CMEUserData *get_group_cme_data(
|
||||
const api::admin::BTSUserGroup &group,
|
||||
const api::admin::AdminData &data);
|
||||
static const api::admin::ICEUserData *get_group_ice_data(
|
||||
const api::admin::BTSUserGroup &group,
|
||||
const api::admin::AdminData &data);
|
||||
static const api::admin::SIMUserData *get_group_sim_data(
|
||||
const api::admin::BTSUserGroup &group,
|
||||
const api::admin::AdminData &data);
|
||||
static const api::admin::UserAccountPermissions *const
|
||||
get_group_user_permissions(
|
||||
const api::admin::AdminData &admin_data, uint32_t group_id);
|
||||
|
||||
static const CMEUserData *get_group_cme_data(
|
||||
const BTSUserGroup &group, const AdminData &data);
|
||||
static const ICEUserData *get_group_ice_data(
|
||||
const BTSUserGroup &group, const AdminData &data);
|
||||
static const SIMUserData *get_group_sim_data(
|
||||
const BTSUserGroup &group, const AdminData &data);
|
||||
static const UserAccountPermissions *const get_group_user_permissions(
|
||||
const bts::api::admin::AdminData &admin_data, uint32_t group_id);
|
||||
////////////////////////////////////////////
|
||||
// group mutators/ /////////////////////////
|
||||
////////////////////////////////////////////
|
||||
static api::admin::BTSUserGroup *create_group(
|
||||
api::admin::AdminData &data,
|
||||
static BTSUserGroup *create_group(
|
||||
AdminData &data,
|
||||
uint32_t parent_group_id,
|
||||
const std::string &group_name,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
|
||||
AdminServiceOperationResult &result);
|
||||
////////////////////////////////////////////
|
||||
// user accessors //////////////////////////
|
||||
////////////////////////////////////////////
|
||||
static api::admin::BTSUser *find_user(
|
||||
api::admin::AdminData &data,
|
||||
static BTSUser *find_user(
|
||||
AdminData &data,
|
||||
const std::string &user_name,
|
||||
bool include_deleted = false);
|
||||
static const api::admin::BTSUser *find_user(
|
||||
const api::admin::AdminData &data,
|
||||
static const BTSUser *find_user(
|
||||
const AdminData &data,
|
||||
const std::string &user_name,
|
||||
bool include_deleted = false);
|
||||
static api::admin::BTSUser *find_user(
|
||||
api::admin::AdminData &data, uint32_t id, bool include_deleted = false);
|
||||
static const api::admin::BTSUser *const find_user(
|
||||
const api::admin::AdminData &data,
|
||||
uint32_t id,
|
||||
bool include_deleted = false);
|
||||
static std::vector<const api::admin::BTSUser *> users(
|
||||
const api::admin::AdminData &data, bool include_deleted);
|
||||
static std::vector<api::admin::BTSUser *> users_mutable(
|
||||
api::admin::AdminData &data, bool include_deleted);
|
||||
static uint32_t find_max_user_id(const api::admin::AdminData &data);
|
||||
static const api::admin::CMEUserData *get_user_cme_data(
|
||||
const api::admin::BTSUser &user, const api::admin::AdminData &data);
|
||||
static const api::admin::ICEUserData *get_user_ice_data(
|
||||
const api::admin::BTSUser &user, const api::admin::AdminData &data);
|
||||
static const api::admin::SIMUserData *get_user_sim_data(
|
||||
const api::admin::BTSUser &user, const api::admin::AdminData &data);
|
||||
static BTSUser *find_user(
|
||||
AdminData &data, uint32_t id, bool include_deleted = false);
|
||||
static const BTSUser *const find_user(
|
||||
const AdminData &data, uint32_t id, bool include_deleted = false);
|
||||
static std::vector<const BTSUser *> users(
|
||||
const AdminData &data, bool include_deleted);
|
||||
static std::vector<BTSUser *> users_mutable(
|
||||
AdminData &data, bool include_deleted);
|
||||
static uint32_t find_max_user_id(const AdminData &data);
|
||||
static const CMEUserData *get_user_cme_data(
|
||||
const BTSUser &user, const AdminData &data);
|
||||
static const ICEUserData *get_user_ice_data(
|
||||
const BTSUser &user, const AdminData &data);
|
||||
static const SIMUserData *get_user_sim_data(
|
||||
const BTSUser &user, const AdminData &data);
|
||||
// finds the "exchange user" (ex: "operator_id" at CME or "authorized_trader"
|
||||
// at ICE) for a user
|
||||
static std::string find_user_exchange_id(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSUser &user,
|
||||
const AdminData &data,
|
||||
const BTSUser &user,
|
||||
api::Venue exchange,
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
AdminServiceOperationResult &out_result);
|
||||
// i. uses the exchange and exchange_user to reverse lookup the user(s)
|
||||
// ii. uses the exchange, connection and exchange_account to reverse lookup
|
||||
// the account(s) iii. finds the first account from ii that a user from i is
|
||||
// authorized to trade
|
||||
static std::string find_user_company_id(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSUser &user,
|
||||
const AdminData &data,
|
||||
const BTSUser &user,
|
||||
api::Venue exchange,
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
AdminServiceOperationResult &out_result);
|
||||
|
||||
static std::pair<const api::admin::BTSAccount *, const api::admin::BTSUser *>
|
||||
static std::pair<const BTSAccount *, const BTSUser *>
|
||||
find_user_account_matches(
|
||||
const api::admin::AdminData &admin_data,
|
||||
const AdminData &admin_data,
|
||||
const std::string &connection,
|
||||
const std::string &exchange_account,
|
||||
const std::string &exchange_user,
|
||||
@@ -302,108 +284,100 @@ class AdminServiceUtils {
|
||||
// for a given user finds the account permissions (if permissions are
|
||||
// derived from the group, it traverses the group hierarcy to find the
|
||||
// correct permissions.
|
||||
static const api::admin::UserAccountPermissions *get_user_permissions(
|
||||
const api::admin::AdminData &admin_data, uint32_t user_id);
|
||||
static const UserAccountPermissions *get_user_permissions(
|
||||
const AdminData &admin_data, uint32_t user_id);
|
||||
// for a given user, fills in the associated_accounts ref arg
|
||||
// with the accounts the user has permission to trade in
|
||||
static void find_user_trading_accounts(
|
||||
const api::admin::AdminData &admin_data,
|
||||
const AdminData &admin_data,
|
||||
uint32_t user_id,
|
||||
std::vector<const api::admin::BTSAccount *> &associated_accounts);
|
||||
std::vector<const BTSAccount *> &associated_accounts);
|
||||
// for a given account, fills in the associated_users ref arg
|
||||
// with the users that can trade into the account
|
||||
static void find_users_for_trading_account(
|
||||
const api::admin::AdminData &admin_data,
|
||||
const AdminData &admin_data,
|
||||
uint32_t account_id,
|
||||
std::vector<const api::admin::BTSUser *> &associated_users);
|
||||
std::vector<const BTSUser *> &associated_users);
|
||||
// validates that changes in user data satisfy all the
|
||||
// constraints
|
||||
static void validate_user_update(
|
||||
const api::admin::AdminData &admin_data,
|
||||
const api::admin::BTSUser &updated_user,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
const AdminData &admin_data,
|
||||
const BTSUser &updated_user,
|
||||
AdminServiceOperationResult &result);
|
||||
// vaidates the exchange part of user data updates
|
||||
static void validate_user_exchange_data(
|
||||
const api::admin::BTSUser &updated_user,
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
const BTSUser &updated_user, AdminServiceOperationResult &out_result);
|
||||
static void get_md_entitlement_report(
|
||||
const api::admin::AdminData &admin_data,
|
||||
std::vector<api::admin::MDEntitlementReportUnit> &entitlements);
|
||||
const AdminData &admin_data,
|
||||
std::vector<MDEntitlementReportUnit> &entitlements);
|
||||
// Gets a reporting unit out of CMEMicData
|
||||
static api::admin::MDEntitlementReportUnit get_cme_md_entitlement_unit(
|
||||
const api::admin::BTSUser &user,
|
||||
const api::admin::CMEMicData &cme_mic_data);
|
||||
static api::admin::MDEntitlementReportUnit get_ice_md_entitlement_unit(
|
||||
const api::admin::BTSUser &user,
|
||||
const api::admin::ICEMicData &ice_mic_data);
|
||||
static MDEntitlementReportUnit get_cme_md_entitlement_unit(
|
||||
const BTSUser &user, const CMEMicData &cme_mic_data);
|
||||
static MDEntitlementReportUnit get_ice_md_entitlement_unit(
|
||||
const BTSUser &user, const ICEMicData &ice_mic_data);
|
||||
static void get_user_symbol_permissions(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSUser &user,
|
||||
std::vector<api::admin::UserSymbolMDPermissionUnit> &res);
|
||||
|
||||
const AdminData &data,
|
||||
const BTSUser &user,
|
||||
std::vector<UserSymbolMDPermissionUnit> &res);
|
||||
////////////////////////////////////////////
|
||||
// user mutators ///////////////////////////
|
||||
////////////////////////////////////////////
|
||||
static api::admin::BTSUser *create_user(
|
||||
api::admin::AdminData &data,
|
||||
static BTSUser *create_user(
|
||||
AdminData &data,
|
||||
uint32_t parent_group_id,
|
||||
const std::string &name,
|
||||
const std::string &password,
|
||||
const std::string &full_name,
|
||||
const std::string &company,
|
||||
bool is_active,
|
||||
const api::admin::LicenseData &license_data,
|
||||
const api::admin::LicenseEntitlements &license_entitlements,
|
||||
api::admin::AdminServiceCreateEntityResult &result);
|
||||
static api::admin::BTSUser *update_user(
|
||||
api::admin::AdminData &admin_data,
|
||||
const api::admin::BTSUser &updated_user,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
const LicenseData &license_data,
|
||||
const LicenseEntitlements &license_entitlements,
|
||||
AdminServiceCreateEntityResult &result);
|
||||
static BTSUser *update_user(
|
||||
AdminData &admin_data,
|
||||
const BTSUser &updated_user,
|
||||
AdminServiceOperationResult &result);
|
||||
static void delete_user(
|
||||
api::admin::AdminData &admin_data,
|
||||
const api::admin::IDAPIArgument &id,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
AdminData &admin_data,
|
||||
const IDAPIArgument &id,
|
||||
AdminServiceOperationResult &result);
|
||||
static void set_user_exchange_data(
|
||||
api::admin::BTSUser *user,
|
||||
const api::admin::ExchangeUserData &exchange_user_data);
|
||||
static void clear_user_accounts(api::admin::BTSUser *user);
|
||||
BTSUser *user, const ExchangeUserData &exchange_user_data);
|
||||
static void clear_user_accounts(BTSUser *user);
|
||||
static void set_user_accounts(
|
||||
api::admin::BTSUser *user,
|
||||
const api::admin::UserAccountPermissions &user_account_permissions);
|
||||
|
||||
BTSUser *user, const UserAccountPermissions &user_account_permissions);
|
||||
////////////////////////////////////////////
|
||||
// risk limits //////////////////////////////
|
||||
////////////////////////////////////////////
|
||||
// sorting function used by sort_limits to
|
||||
// sort SecurityScope's
|
||||
static bool _sort_security_scopes(
|
||||
const api::admin::SecurityScope &a, const api::admin::SecurityScope &b);
|
||||
const SecurityScope &a, const SecurityScope &b);
|
||||
// sorting function used by sort_limits to
|
||||
// sort ProductScope's
|
||||
static bool _sort_product_scopes(
|
||||
const api::admin::ProductScope &a, const api::admin::ProductScope &);
|
||||
const ProductScope &a, const ProductScope &);
|
||||
// this sorts the rules such that the most
|
||||
// specific ones are at the top. this makes it
|
||||
// easier to match rules with securities/products
|
||||
static void sort_limits(api::admin::BTSAccount &account);
|
||||
// creates a copy of api::admin::AdminData with sorted rules
|
||||
static api::admin::AdminData sort_limits(
|
||||
const api::admin::AdminData &admin_data);
|
||||
static void sort_limits(BTSAccount &account);
|
||||
// creates a copy of AdminData with sorted rules
|
||||
static AdminData sort_limits(const AdminData &admin_data);
|
||||
// find the clip size for a security, account
|
||||
static const uint32_t find_account_security_clip_size(
|
||||
const api::admin::BTSAccount &account, const api::Security &security);
|
||||
const BTSAccount &account, const api::Security &security);
|
||||
// find the position limit for an account, security
|
||||
static const api::admin::AccountSecurityPositionLimitUnit *
|
||||
static const AccountSecurityPositionLimitUnit *
|
||||
find_account_security_position_limit(
|
||||
const api::admin::BTSAccount &account, const api::Security &security);
|
||||
const BTSAccount &account, const api::Security &security);
|
||||
// find the position limit for an account, product
|
||||
static const api::admin::AccountProductPositionLimitUnit *
|
||||
static const AccountProductPositionLimitUnit *
|
||||
find_account_product_position_limit(
|
||||
const api::admin::BTSAccount &account, const api::Product &product);
|
||||
const BTSAccount &account, const api::Product &product);
|
||||
static bool match_security_scope(
|
||||
const api::admin::SecurityScope &scope, const api::Security &security);
|
||||
const SecurityScope &scope, const api::Security &security);
|
||||
static bool match_product_scope(
|
||||
const api::admin::ProductScope &scope, const api::Product &product);
|
||||
const ProductScope &scope, const api::Product &product);
|
||||
static bool match_product(
|
||||
const api::Security &security, const api::Product &product);
|
||||
static bool _match_limit_rule_symbol(
|
||||
@@ -414,14 +388,13 @@ class AdminServiceUtils {
|
||||
////////////////////////////////////////////
|
||||
static bool has_id(uint32_t id);
|
||||
static bool is_symbol_set(const std::string &symbol);
|
||||
static void bootstrap_data(api::admin::AdminData &data);
|
||||
static void bootstrap_data(bts::api::admin::AdminData &data);
|
||||
static void get_spread_prod_qty(
|
||||
const api::Security &s,
|
||||
std::map<std::string, double> &out_map,
|
||||
double mult = 1.0);
|
||||
// returns true if this spread effects the position of any outrights
|
||||
static bool residual_pos_spread(const api::Security &s);
|
||||
};
|
||||
|
||||
};
|
||||
} // namespace admin
|
||||
} // namespace bts
|
||||
@@ -1,174 +0,0 @@
|
||||
#include "OrderService.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
|
||||
namespace bts::api::orderman {
|
||||
|
||||
#define PARSE_STRING(name, dest, it) \
|
||||
it = j.find(#name); \
|
||||
if (it != j.end()) { \
|
||||
dest.mutable_##name()->clear(); \
|
||||
it->get_to(*dest.mutable_##name()); \
|
||||
}
|
||||
#define PARSE_VALUE(name, dest, it) \
|
||||
it = j.find(#name); \
|
||||
if (it != j.end()) { \
|
||||
dest.set_##name(it->get<decltype(dest.name())>()); \
|
||||
}
|
||||
#define PARSE_STRUCT(name, dest, it) \
|
||||
it = j.find(#name); \
|
||||
if (it != j.end()) { \
|
||||
it->get_to(*dest.mutable_##name()); \
|
||||
}
|
||||
#define PARSE_ARRAY(name, dest, it) \
|
||||
it = j.find(#name); \
|
||||
if (it != j.end() && it->is_array()) { \
|
||||
c.mutable_##name()->Clear(); \
|
||||
it->get_to(*c.mutable_##name()); \
|
||||
}
|
||||
#define PARSE_ONEOF(name, dest, it) \
|
||||
if (!parsed_oneof) { \
|
||||
it = j.find(#name); \
|
||||
if (it != j.end()) { \
|
||||
it->get_to(*dest.mutable_##name()); \
|
||||
parsed_oneof = true; \
|
||||
} \
|
||||
}
|
||||
#define PARSE_ONEOFS(for_each) \
|
||||
{ \
|
||||
bool parsed_oneof = false; \
|
||||
for_each(PARSE_ONEOF) \
|
||||
}
|
||||
|
||||
#define WRITE_VALUE(src, name) j[#name] = src.name()
|
||||
#define WRITE_STRUCT(src, name) \
|
||||
if (src.has_##name()) { \
|
||||
j[#name] = src.name(); \
|
||||
}
|
||||
|
||||
#define WRITE_IF_NOT_ZERO(src, name) \
|
||||
if (src.name() != 0) { \
|
||||
j[#name] = src.name(); \
|
||||
}
|
||||
#define WRITE_IF_NOT_NAN(src, name) \
|
||||
if (!std::isnan(src.name())) { \
|
||||
j[#name] = src.name(); \
|
||||
}
|
||||
#define WRITE_IF_NOT_EMPTY(src, name) \
|
||||
if (!src.name().empty()) { \
|
||||
j[#name] = src.name(); \
|
||||
}
|
||||
#define WRITE_TIMESTAMP(src, name) \
|
||||
j[#name] = bts::core::TimeStamp::from_nanos( \
|
||||
src.name().seconds() * 1'000'000'000 + src.name().nanos()) \
|
||||
.to_iso_string()
|
||||
|
||||
#define PARSE_TIMESTAMP(name, dest, it) \
|
||||
it = j.find(#name); \
|
||||
if (it != j.end()) { \
|
||||
bts::core::TimeStamp tmp_ts = \
|
||||
bts::core::TimeStamp::from_iso_string(it->get<std::string>()); \
|
||||
tmp_ts.to_protobuf(*dest.mutable_##name()); \
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const Fill &f) {
|
||||
WRITE_IF_NOT_ZERO(f, order_key);
|
||||
WRITE_IF_NOT_ZERO(f, security_key);
|
||||
WRITE_IF_NOT_EMPTY(f, account);
|
||||
WRITE_IF_NOT_NAN(f, last_price);
|
||||
WRITE_IF_NOT_NAN(f, avg_price);
|
||||
WRITE_VALUE(f, side);
|
||||
WRITE_IF_NOT_ZERO(f, last_shares);
|
||||
WRITE_IF_NOT_EMPTY(f, fill_key);
|
||||
WRITE_TIMESTAMP(f, timestamp);
|
||||
WRITE_IF_NOT_ZERO(f, internal_account_id);
|
||||
WRITE_VALUE(f, fill_kind);
|
||||
WRITE_VALUE(f, fill_status);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, Fill &f) {
|
||||
nlohmann::json::const_iterator it;
|
||||
PARSE_VALUE(order_key, f, it);
|
||||
PARSE_VALUE(security_key, f, it);
|
||||
PARSE_STRING(account, f, it);
|
||||
PARSE_VALUE(last_price, f, it);
|
||||
PARSE_VALUE(avg_price, f, it);
|
||||
PARSE_VALUE(side, f, it);
|
||||
PARSE_VALUE(last_shares, f, it);
|
||||
PARSE_STRING(fill_key, f, it);
|
||||
PARSE_TIMESTAMP(timestamp, f, it);
|
||||
PARSE_VALUE(internal_account_id, f, it);
|
||||
PARSE_VALUE(fill_kind, f, it);
|
||||
PARSE_VALUE(fill_status, f, it);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const NativeOrderState &n) {
|
||||
WRITE_VALUE(n, exchange_order_id);
|
||||
WRITE_VALUE(n, last_msg_seq_num);
|
||||
WRITE_VALUE(n, sub_error_code);
|
||||
WRITE_VALUE(n, native_status);
|
||||
WRITE_VALUE(n, routing_record);
|
||||
WRITE_VALUE(n, native_type);
|
||||
WRITE_VALUE(n, last_transact_time);
|
||||
WRITE_VALUE(n, last_transact_time_unix_ns);
|
||||
WRITE_VALUE(n, stop_px);
|
||||
WRITE_VALUE(n, exchange_order_id_u64);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, NativeOrderState &n) {
|
||||
nlohmann::json::const_iterator it;
|
||||
PARSE_STRING(exchange_order_id, n, it);
|
||||
PARSE_VALUE(last_msg_seq_num, n, it);
|
||||
PARSE_VALUE(sub_error_code, n, it);
|
||||
PARSE_VALUE(native_status, n, it);
|
||||
PARSE_STRUCT(routing_record, n, it);
|
||||
PARSE_VALUE(native_type, n, it);
|
||||
PARSE_STRING(last_transact_time, n, it);
|
||||
PARSE_VALUE(last_transact_time_unix_ns, n, it);
|
||||
PARSE_VALUE(stop_px, n, it);
|
||||
PARSE_VALUE(exchange_order_id_u64, n, it);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const RoutingRecord &r) {
|
||||
WRITE_IF_NOT_EMPTY(r, session);
|
||||
WRITE_IF_NOT_EMPTY(r, account);
|
||||
WRITE_IF_NOT_EMPTY(r, operator_id);
|
||||
WRITE_VALUE(r, cti_code);
|
||||
WRITE_VALUE(r, customer_or_firm);
|
||||
WRITE_IF_NOT_EMPTY(r, smp_id);
|
||||
WRITE_VALUE(r, smp_instruction);
|
||||
WRITE_IF_NOT_EMPTY(r, location);
|
||||
WRITE_IF_NOT_EMPTY(r, cust_order_handing_inst);
|
||||
WRITE_IF_NOT_EMPTY(r, sender_compid);
|
||||
WRITE_IF_NOT_EMPTY(r, clearing_firm);
|
||||
WRITE_IF_NOT_EMPTY(r, customer_account_ref_id);
|
||||
WRITE_IF_NOT_EMPTY(r, account_code);
|
||||
WRITE_IF_NOT_EMPTY(r, company_id);
|
||||
WRITE_IF_NOT_EMPTY(r, authorized_trader);
|
||||
WRITE_IF_NOT_EMPTY(r, authorized_group);
|
||||
WRITE_IF_NOT_EMPTY(r, cust_order_handing_inst);
|
||||
WRITE_IF_NOT_EMPTY(r, order_server_id);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, RoutingRecord &r) {
|
||||
nlohmann::json::const_iterator it;
|
||||
PARSE_STRING(session, r, it);
|
||||
PARSE_STRING(account, r, it);
|
||||
PARSE_STRING(operator_id, r, it);
|
||||
PARSE_VALUE(cti_code, r, it);
|
||||
PARSE_VALUE(customer_or_firm, r, it);
|
||||
PARSE_STRING(smp_id, r, it);
|
||||
PARSE_VALUE(smp_instruction, r, it);
|
||||
PARSE_STRING(location, r, it);
|
||||
PARSE_STRING(cust_order_handling_inst, r, it);
|
||||
PARSE_STRING(sender_compid, r, it);
|
||||
PARSE_STRING(clearing_firm, r, it);
|
||||
PARSE_STRING(customer_account_ref_id, r, it);
|
||||
PARSE_STRING(account_code, r, it);
|
||||
PARSE_STRING(company_id, r, it);
|
||||
PARSE_STRING(authorized_trader, r, it);
|
||||
PARSE_STRING(authorized_group, r, it);
|
||||
PARSE_STRING(cust_order_handing_inst, r, it);
|
||||
PARSE_STRING(order_server_id, r, it);
|
||||
}
|
||||
|
||||
} // namespace bts::api::orderman
|
||||
@@ -1,10 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/api/services/native_order_type.h"
|
||||
#include "lib/api/services/order_status.h"
|
||||
#include "lib/api/services/time_in_force.h"
|
||||
#include "lib/api/services/trigger_price_type.h"
|
||||
#include "lib/core/EnumMacros.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
@@ -14,70 +10,22 @@
|
||||
|
||||
namespace bts::api::orderman {
|
||||
|
||||
PB_ENUM_FROM_JSON(Fill_FillKind)
|
||||
PB_ENUM_TO_JSON(Fill_FillKind)
|
||||
PB_ENUM_FROM_JSON(OrderStatus)
|
||||
PB_ENUM_TO_JSON(OrderStatus)
|
||||
|
||||
PB_ENUM_FROM_JSON(Fill_FillStatus)
|
||||
PB_ENUM_TO_JSON(Fill_FillStatus)
|
||||
|
||||
void to_json(nlohmann::json &j, const Fill &f);
|
||||
void from_json(const nlohmann::json &j, Fill &f);
|
||||
|
||||
PB_ENUM_FROM_JSON(OrderSubType)
|
||||
PB_ENUM_TO_JSON(OrderSubType)
|
||||
|
||||
PB_ENUM_FROM_JSON(SelfMatchPreventionInstruction)
|
||||
PB_ENUM_TO_JSON(SelfMatchPreventionInstruction)
|
||||
|
||||
PB_ENUM_FROM_JSON(CustomerOrFirm)
|
||||
PB_ENUM_TO_JSON(CustomerOrFirm)
|
||||
|
||||
void to_json(nlohmann::json &j, const RoutingRecord &r);
|
||||
void from_json(const nlohmann::json &j, RoutingRecord &r);
|
||||
|
||||
PB_ENUM_FROM_JSON(NativeOrderStatus)
|
||||
PB_ENUM_TO_JSON(NativeOrderStatus)
|
||||
|
||||
PB_ENUM_FROM_JSON(NativeOrderState_SubError)
|
||||
PB_ENUM_TO_JSON(NativeOrderState_SubError)
|
||||
|
||||
void to_json(nlohmann::json &j, const NativeOrderState &n);
|
||||
void from_json(const nlohmann::json &j, NativeOrderState &n);
|
||||
PB_ENUM_FROM_JSON(TriggerPriceType)
|
||||
PB_ENUM_TO_JSON(TriggerPriceType)
|
||||
|
||||
} // namespace bts::api::orderman
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::Fill_FillKind,
|
||||
k,
|
||||
"{}",
|
||||
bts::api::orderman::Fill_FillKind_Name(k));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::Fill_FillStatus,
|
||||
bts::api::orderman::OrderStatus,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::Fill_FillStatus(s));
|
||||
bts::api::orderman::OrderStatus_Name(s));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::OrderSubType,
|
||||
bts::api::orderman::TriggerPriceType,
|
||||
t,
|
||||
"{}",
|
||||
bts::api::orderman::OrderSubType_Name(t));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::SelfMatchPreventionInstruction,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::SelfMatchPreventionInstruction_Name(s));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::NativeOrderStatus,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::NativeOrderStatus_Name(s));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::NativeOrderState_SubError,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::NativeOrderState_SubError_Name(s));
|
||||
bts::api::orderman::TriggerPriceType_Name(t));
|
||||
@@ -1,57 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/core/EnumMacros.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/string_utils.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace bts::api::orderman {
|
||||
|
||||
/**
|
||||
* Parses NativeOrderType from a string. Unlike `bts::api::orderman::NativeOrderType_Parse`,
|
||||
* this implementation is case insensitive, and makes the 'NATIVE_ORDER_TYPE_' prefix optional.
|
||||
*/
|
||||
bool native_order_type_parse_smart(
|
||||
const std::string &str, NativeOrderType *result);
|
||||
|
||||
PB_ENUM_FROM_JSON(NativeOrderType)
|
||||
PB_ENUM_TO_JSON(NativeOrderType)
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, NativeOrderType t) noexcept;
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline bool native_order_type_parse_smart(
|
||||
const std::string &str, NativeOrderType *result) {
|
||||
if (NativeOrderType_Parse(str, result)) {
|
||||
return true;
|
||||
}
|
||||
std::string s = bts::system::string_utils::copy_to_upper(str);
|
||||
if (NativeOrderType_Parse(s, result)) {
|
||||
return true;
|
||||
}
|
||||
const char *prefix = "NATIVE_ORDER_TYPE_";
|
||||
if (!s.starts_with(prefix)) {
|
||||
s.insert(0, prefix);
|
||||
}
|
||||
return NativeOrderType_Parse(s, result);
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, NativeOrderType t) noexcept {
|
||||
out << NativeOrderType_Name(t);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace bts::api::orderman
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::NativeOrderType,
|
||||
t,
|
||||
"{}",
|
||||
bts::api::orderman::NativeOrderType_Name(t));
|
||||
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/core/EnumMacros.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/string_utils.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace bts::api::orderman {
|
||||
|
||||
/**
|
||||
* Parses OrderStatus from a string. Unlike `bts::api::orderman::OrderStatus_Parse`,
|
||||
* this implementation is case insensitive, and makes the 'ORDER_STATUS_' prefix optional.
|
||||
*/
|
||||
bool order_status_parse_smart(const std::string &str, OrderStatus *result);
|
||||
|
||||
PB_ENUM_FROM_JSON(OrderStatus)
|
||||
PB_ENUM_TO_JSON(OrderStatus)
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, OrderStatus s) noexcept;
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline bool order_status_parse_smart(
|
||||
const std::string &str, OrderStatus *result) {
|
||||
if (OrderStatus_Parse(str, result)) {
|
||||
return true;
|
||||
}
|
||||
std::string s = bts::system::string_utils::copy_to_upper(str);
|
||||
if (OrderStatus_Parse(s, result)) {
|
||||
return true;
|
||||
}
|
||||
const char *prefix = "ORDER_STATUS_";
|
||||
if (!s.starts_with(prefix)) {
|
||||
s.insert(0, prefix);
|
||||
}
|
||||
return OrderStatus_Parse(s, result);
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, OrderStatus s) noexcept {
|
||||
out << OrderStatus_Name(s);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace bts::api::orderman
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::OrderStatus,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::OrderStatus_Name(s));
|
||||
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/core/EnumMacros.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/string_utils.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace bts::api::orderman {
|
||||
|
||||
/**
|
||||
* Parses TimeInForce from a string. Unlike `bts::api::orderman::TimeInForce_Parse`,
|
||||
* this implementation is case insensitive, and makes the 'TIME_IN_FORCE_' prefix optional.
|
||||
*/
|
||||
bool time_in_force_parse_smart(const std::string &str, TimeInForce *result);
|
||||
|
||||
PB_ENUM_FROM_JSON(TimeInForce)
|
||||
PB_ENUM_TO_JSON(TimeInForce)
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, TimeInForce tif) noexcept;
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline bool time_in_force_parse_smart(
|
||||
const std::string &str, TimeInForce *result) {
|
||||
if (TimeInForce_Parse(str, result)) {
|
||||
return true;
|
||||
}
|
||||
std::string s = bts::system::string_utils::copy_to_upper(str);
|
||||
if (TimeInForce_Parse(s, result)) {
|
||||
return true;
|
||||
}
|
||||
const char *prefix = "TIME_IN_FORCE_";
|
||||
if (!s.starts_with(prefix)) {
|
||||
s.insert(0, prefix);
|
||||
}
|
||||
return TimeInForce_Parse(s, result);
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, TimeInForce tif) noexcept {
|
||||
out << TimeInForce_Name(tif);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace bts::api::orderman
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::TimeInForce,
|
||||
v,
|
||||
"{}",
|
||||
bts::api::orderman::TimeInForce_Name(v));
|
||||
@@ -1,58 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/core/EnumMacros.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/string_utils.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace bts::api::orderman {
|
||||
|
||||
/**
|
||||
* Parses TriggerPriceType from a string. Unlike `bts::api::orderman::TriggerPriceType_Parse`,
|
||||
* this implementation is case insensitive, and makes the 'TRIGGER_PRICE_TYPE_' prefix optional.
|
||||
*/
|
||||
bool trigger_price_type_parse_smart(
|
||||
const std::string &str, TriggerPriceType *result);
|
||||
|
||||
PB_ENUM_FROM_JSON(TriggerPriceType)
|
||||
PB_ENUM_TO_JSON(TriggerPriceType)
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, TriggerPriceType t) noexcept;
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline bool trigger_price_type_parse_smart(
|
||||
const std::string &str, TriggerPriceType *result) {
|
||||
if (TriggerPriceType_Parse(str, result)) {
|
||||
return true;
|
||||
}
|
||||
std::string s = bts::system::string_utils::copy_to_upper(str);
|
||||
if (TriggerPriceType_Parse(s, result)) {
|
||||
return true;
|
||||
}
|
||||
const char *prefix = "TRIGGER_PRICE_TYPE_";
|
||||
if (!s.starts_with(prefix)) {
|
||||
s.insert(0, prefix);
|
||||
}
|
||||
return TriggerPriceType_Parse(s, result);
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(
|
||||
std::ostream &out, TriggerPriceType t) noexcept {
|
||||
out << TriggerPriceType_Name(t);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace bts::api::orderman
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::TriggerPriceType,
|
||||
t,
|
||||
"{}",
|
||||
bts::api::orderman::TriggerPriceType_Name(t));
|
||||
@@ -65,7 +65,6 @@ function(get_cpp_sources dest)
|
||||
domain/DomainSecurityUtils.cpp
|
||||
domain/SecurityCriteria.cpp
|
||||
domain/SecurityCriteriaClause.cpp
|
||||
domain/synthetic_book_side.cpp
|
||||
domain_model/Security.cpp
|
||||
domain_model/SecurityTests.cpp
|
||||
domain_model/sod.cpp
|
||||
@@ -75,7 +74,6 @@ function(get_cpp_sources dest)
|
||||
expression/ParserExceptions.cpp
|
||||
expression/Value.cpp
|
||||
expression/tests/expression_parser_tests.cpp
|
||||
io/JsonReader.cpp
|
||||
logging/LoggerHub.cpp
|
||||
logging/Sink.cpp
|
||||
logging/logging.cpp
|
||||
|
||||
@@ -64,7 +64,6 @@ function(get_cpp_sources dest)
|
||||
domain/DomainSecurityUtils.cpp
|
||||
domain/SecurityCriteria.cpp
|
||||
domain/SecurityCriteriaClause.cpp
|
||||
domain/synthetic_book_side.cpp
|
||||
domain_model/Security.cpp
|
||||
domain_model/SecurityTests.cpp
|
||||
domain_model/sod.cpp
|
||||
@@ -74,7 +73,6 @@ function(get_cpp_sources dest)
|
||||
expression/ParserExceptions.cpp
|
||||
expression/Value.cpp
|
||||
expression/tests/expression_parser_tests.cpp
|
||||
io/JsonReader.cpp
|
||||
logging/LoggerHub.cpp
|
||||
logging/Sink.cpp
|
||||
logging/logging.cpp
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/utils/LockBox.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/type_name.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace bts::collections {
|
||||
|
||||
template <class KeyT, class ValueT>
|
||||
class ConcurrentFactory {
|
||||
utils::LockBox<std::unordered_map<KeyT, std::shared_ptr<ValueT>>> store;
|
||||
|
||||
public:
|
||||
std::shared_ptr<ValueT> add(
|
||||
const KeyT &key,
|
||||
std::function<void(std::shared_ptr<ValueT> &)> callback) {
|
||||
return store.lock([&](auto &map) {
|
||||
auto [it, is_new] = map.try_emplace(key, std::make_shared<ValueT>());
|
||||
bts_assert_msgf(
|
||||
is_new, "{}::add. Duplicate key '{}'",
|
||||
utils::cached_type_name<ConcurrentFactory<KeyT, ValueT>>(), key);
|
||||
callback(it->second);
|
||||
return it->second;
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t erase(const KeyT &key) {
|
||||
return store.lock([&](auto &map) {
|
||||
map.erase(key);
|
||||
return map.size();
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t size() const {
|
||||
return store.lock([&](auto &map) { return map.size(); });
|
||||
}
|
||||
|
||||
template <
|
||||
typename CallbackT = std::function<void(std::shared_ptr<ValueT> &)>>
|
||||
std::shared_ptr<const ValueT> get(const KeyT &key, CallbackT callback) {
|
||||
return store.lock([&](auto &map) {
|
||||
auto [it, is_new] = map.try_emplace(key, std::make_shared<ValueT>());
|
||||
bts_assert_msgf(
|
||||
!is_new, "{}::get. Key not found '{}'",
|
||||
utils::cached_type_name<ConcurrentFactory<KeyT, ValueT>>(), key);
|
||||
callback(it->second);
|
||||
return it->second;
|
||||
});
|
||||
}
|
||||
|
||||
template <
|
||||
typename CallbackT =
|
||||
std::function<void(std::shared_ptr<ValueT> &, bool)>>
|
||||
std::shared_ptr<const ValueT> get_or_add(
|
||||
const KeyT &key, CallbackT callback) {
|
||||
return store.lock([&](auto &map) {
|
||||
auto [it, is_new] = map.try_emplace(key);
|
||||
if (is_new) {
|
||||
it->second = std::make_shared<ValueT>();
|
||||
}
|
||||
callback(it->second, is_new);
|
||||
return it->second;
|
||||
});
|
||||
}
|
||||
|
||||
template <
|
||||
typename CallbackT =
|
||||
std::function<void(std::shared_ptr<ValueT> &, bool)>>
|
||||
std::shared_ptr<const ValueT> replace(const KeyT &key, CallbackT callback) {
|
||||
return store.lock([&](auto &map) {
|
||||
auto [it, is_new] = map.try_emplace(key);
|
||||
bts_assert_msgf(
|
||||
!is_new, "{}::replace. Key not found '{}'",
|
||||
utils::cached_type_name<ConcurrentFactory<KeyT, ValueT>>(), key);
|
||||
it->second = std::make_shared<ValueT>();
|
||||
callback(it->second, is_new);
|
||||
return it->second;
|
||||
});
|
||||
}
|
||||
|
||||
template <
|
||||
typename CallbackT =
|
||||
std::function<void(std::shared_ptr<ValueT> &, bool)>>
|
||||
std::shared_ptr<const ValueT> replace_or_add(
|
||||
const KeyT &key, CallbackT callback) {
|
||||
return store.lock([&](auto &map) {
|
||||
auto [it, is_new] = map.try_emplace(key);
|
||||
it->second = std::make_shared<ValueT>();
|
||||
callback(it->second, is_new);
|
||||
return it->second;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace bts::collections
|
||||
Loaded 100 of 477 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user