Compare commits
103
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
021431b567 | ||
|
|
e7b426cd3d | ||
|
|
ee02c3ffa7 | ||
|
|
21b725e5df | ||
|
|
649931ae3c | ||
|
|
7b5f034ed2 | ||
|
|
74b376ce56 | ||
|
|
fc96b9e436 | ||
|
|
fd015577b2 | ||
|
|
8d142b8a74 | ||
|
|
8210b9f35f | ||
|
|
12b20b05ce | ||
|
|
72efbccdbd | ||
|
|
33643e9eab | ||
|
|
0f6dc6f40b | ||
|
|
8c778f5e41 | ||
|
|
f308654200 | ||
|
|
bdf5b980fc | ||
|
|
914ac597cd | ||
|
|
4aa6a77680 | ||
|
|
6b59dd84ee | ||
|
|
4e2c5f2d17 | ||
|
|
9a1b94a1b0 | ||
|
|
13b3c55771 | ||
|
|
d0a6fcdb9c | ||
|
|
1eb13028c1 | ||
|
|
424e5ff16b | ||
|
|
bed389885b | ||
|
|
85cf25a57a | ||
|
|
61471d4675 | ||
|
|
d83435d49d | ||
|
|
582fc10dce | ||
|
|
5969985d24 | ||
|
|
fabe7d665c | ||
|
|
7ce75bb8e7 | ||
|
|
b8e1c3cd60 | ||
|
|
4909944694 | ||
|
|
7258af09b4 | ||
|
|
7f2ce6c1a9 | ||
|
|
4835725e1c | ||
|
|
6a9e8de475 | ||
|
|
4639951acc | ||
|
|
d8de211aac | ||
|
|
87394bc60b | ||
|
|
beba30f65c | ||
|
|
2d58424d8f | ||
|
|
d1bbfa3791 | ||
|
|
aab17f460f | ||
|
|
461ef53322 | ||
|
|
2486bc90b2 | ||
|
|
1c5cbaf60b | ||
|
|
b8de447cfb | ||
|
|
8bfc2d40f9 | ||
|
|
c09fd71c94 | ||
|
|
f3b037553e | ||
|
|
d386d93166 | ||
|
|
5217742c71 | ||
|
|
c59ec95460 | ||
|
|
70f01583ec | ||
|
|
66d2f9eb63 | ||
|
|
4625bed8b8 | ||
|
|
f18fb332b5 | ||
|
|
8746a6f5ca | ||
|
|
702d47abb5 | ||
|
|
bf2a54717c | ||
|
|
9db779fb25 | ||
|
|
f55ddec708 | ||
|
|
6e942dd2d4 | ||
|
|
142310a17a | ||
|
|
7f99a89a95 | ||
|
|
bfce663b4b | ||
|
|
33c743643d | ||
|
|
858e0a389b | ||
|
|
9c3bb85674 | ||
|
|
f4930e8cd4 | ||
|
|
f21e7f3602 | ||
|
|
b56c35aca4 | ||
|
|
e6ac83196e | ||
|
|
7b6f74fc4a | ||
|
|
f754953fac | ||
|
|
2dd1143d9f | ||
|
|
e1ee3275d8 | ||
|
|
2549e406f3 | ||
|
|
d5024f1858 | ||
|
|
3623ac96db | ||
|
|
19834625f9 | ||
|
|
c8fc649b6b | ||
|
|
6b1fc1c413 | ||
|
|
6f086532ac | ||
|
|
dc66282225 | ||
|
|
e2c74c0bb3 | ||
|
|
e89d50eec1 | ||
|
|
8bebbc278d | ||
|
|
2e6d12c9c1 | ||
|
|
102a5e23ea | ||
|
|
582dc791db | ||
|
|
afb35d8c25 | ||
|
|
3a32ed5fe6 | ||
|
|
4e01697327 | ||
|
|
22032c1742 | ||
|
|
0a9bce317c | ||
|
|
40e44db597 | ||
|
|
4a447e44e1 |
No files matched your search
@@ -1,15 +1,10 @@
|
||||
{
|
||||
"dockerFile": "Dockerfile",
|
||||
// Creates local dir to mount to HOME in docker
|
||||
"initializeCommand": "mkdir -p ${localEnv:HOME}/.bts/devcontainer-mounts/${devcontainerId}",
|
||||
"hostRequirements": {
|
||||
"memory": "4gb",
|
||||
"storage": "6gb"
|
||||
"memory": "8gb",
|
||||
"storage": "10gb"
|
||||
},
|
||||
"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
|
||||
@@ -17,7 +12,7 @@
|
||||
// example, if 8000-8003 is taken, go with 9000-9003, then 10000-10003, etc.
|
||||
"runArgs": [
|
||||
"--shm-size",
|
||||
"2gb",
|
||||
"8gb",
|
||||
"-p",
|
||||
"8000:8000",
|
||||
"-p",
|
||||
@@ -29,22 +24,15 @@
|
||||
"-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"
|
||||
"ms-vscode.cpptools",
|
||||
"xaver.clang-format"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+62
@@ -699,6 +699,24 @@
|
||||
"color": "terminal.ansiGreen",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Run Sparkd (Skip Build)",
|
||||
"type": "process",
|
||||
"command": "",
|
||||
"linux": {
|
||||
"command": "sparkd",
|
||||
},
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
"color": "terminal.ansiGreen",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Run Sparkd Config Query",
|
||||
"dependsOn": "Build Sparkd",
|
||||
@@ -740,6 +758,45 @@
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"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",
|
||||
@@ -864,5 +921,10 @@
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
{
|
||||
"id": "sparkdTest",
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -8,6 +8,7 @@ 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).
|
||||
@@ -114,13 +115,59 @@ 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);
|
||||
{
|
||||
double bid_price, ask_price;
|
||||
float bid_sz, ask_sz;
|
||||
call_sec_md.Value.md.get_bbo(out bid_price, out bid_sz, out ask_price, out ask_sz);
|
||||
double call_mid = (bid_price + ask_price) / 2;
|
||||
tp.call = call_mid;
|
||||
}
|
||||
{
|
||||
var put_sec_md = sc_data.get_option(exchange, sym, exp, strike, PutOrCall.Put);
|
||||
double bid_price, ask_price;
|
||||
float bid_sz, ask_sz;
|
||||
put_sec_md.Value.md.get_bbo(out bid_price, out bid_sz, out ask_price, out ask_sz);
|
||||
double put_mid = (bid_price + 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() {
|
||||
// int i = 0;
|
||||
// if (i < Int32.MaxValue) {
|
||||
// return;
|
||||
// }
|
||||
var timer = Stopwatch.StartNew();
|
||||
var res = new Dictionary<uint, GreeksTuple>();
|
||||
var driver_map = _bb.read_state(_drivers_tag);
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Apache.Arrow;
|
||||
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
@@ -161,11 +163,11 @@ namespace mbts.spark {
|
||||
if (!sec_md_map.TryGetValue(id.Value, out sec_md)) {
|
||||
return null;
|
||||
}
|
||||
NativePriceSize bid, ask;
|
||||
ulong time = 0;
|
||||
sec_md.md.get_bbo(ref time, out bid, out ask);
|
||||
double bid_price, ask_price;
|
||||
float bid_sz, ask_sz;
|
||||
sec_md.md.get_bbo(out bid_price, out bid_sz, out ask_price, out ask_sz);
|
||||
return Tuple.Create(sec_md.security,
|
||||
new VolSurfaceMDUnit(bid, ask, sec_md.md.fixing_price));
|
||||
new VolSurfaceMDUnit(bid_price, bid_sz, ask_price, ask_sz, sec_md.md.fixing_price));
|
||||
}
|
||||
|
||||
public Tuple<SecurityImmutable, VolSurfaceMDUnit> get_exp_md(SecurityExpiration sec_exp) {
|
||||
|
||||
@@ -281,5 +281,16 @@ 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 inout
|
||||
// 2. vs input
|
||||
// 3. exp params(drivers, interst rate etc)
|
||||
// 4. driver prices
|
||||
// 5. vol time
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
using bts.api;
|
||||
|
||||
using mbts.co2;
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace bts {
|
||||
public class LocalMDConsumerHandle : mbts.co2.IMDConsumerHandle {
|
||||
private readonly SecurityImmutable _sec;
|
||||
private readonly LocalMDDaemon.BBO _bbo;
|
||||
// private static NativePriceSize bid_nps = new NativePriceSize(new IntPrice(123.45), Convert.ToSingle(100));
|
||||
// private static NativePriceSize ask_nps = new NativePriceSize(new IntPrice(123.46), Convert.ToSingle(100));
|
||||
public LocalMDConsumerHandle(SecurityImmutable sec, LocalMDDaemon.BBO bbo) {
|
||||
_sec = sec;
|
||||
_bbo = bbo;
|
||||
}
|
||||
|
||||
public UInt32 key => _sec.Id;
|
||||
// The exchange key can change at CBOE. This numeric_id will always be correct
|
||||
public ulong numeric_id => Convert.ToUInt64(_sec.Cboe.ExchangeId);
|
||||
|
||||
public void set_bbo(double bid_price, UInt32 bid_qty, double ask_price, UInt32 ask_qty) {
|
||||
_bbo.bid_price = bid_price;
|
||||
_bbo.bid_qty = bid_qty;
|
||||
_bbo.ask_price = ask_price;
|
||||
_bbo.ask_qty = ask_qty;
|
||||
}
|
||||
|
||||
public bool is_heart_beating => true;
|
||||
public bool has_depth => true;
|
||||
|
||||
// This property exists for specific performance optimization purposes
|
||||
// Most callers should just use get_bbo/get_depth
|
||||
public ulong market_timestamp => 0;
|
||||
public ulong misc_timestamp => 0;
|
||||
|
||||
public ulong total_volume => 0;
|
||||
|
||||
public double settlement_price => Double.NaN;
|
||||
public DateTime settlement_date => DateTime.Now;
|
||||
public double previous_settlement_price => Double.NaN;
|
||||
public DateTime previous_settlement_date => DateTime.Now;
|
||||
|
||||
public double open_price => Double.NaN;
|
||||
public DateTime open_date => DateTime.Now;
|
||||
|
||||
public double close_price => Double.NaN;
|
||||
public DateTime close_date => DateTime.Now;
|
||||
public double session_high => Double.NaN;
|
||||
public double session_low => Double.NaN;
|
||||
public double fixing_price => Double.NaN;
|
||||
|
||||
|
||||
public bool get_bbo(out double bid_price, out float bid_sz, out double ask_price, out float ask_sz) {
|
||||
bid_price = _bbo.bid_price;
|
||||
bid_sz = _bbo.bid_qty;
|
||||
ask_price = _bbo.ask_price;
|
||||
ask_sz = _bbo.ask_qty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool get_bbo(ref ulong time, out NativePriceSize bid, out NativePriceSize ask) {
|
||||
bid = new NativePriceSize(new IntPrice(_bbo.bid_price), Convert.ToSingle(_bbo.bid_qty));
|
||||
ask = new NativePriceSize(new IntPrice(_bbo.ask_price), Convert.ToSingle(_bbo.ask_qty));
|
||||
// bid = bid_nps;
|
||||
// ask = ask_nps;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool get_depth(
|
||||
ref ulong time, ref NativePriceSize[] bids, ref NativePriceSize[] asks, uint depth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool get_last_trade(
|
||||
ref ulong time, out NativePriceSize ps, out DateTime timestamp, out ulong total_volume) {
|
||||
ps = new NativePriceSize();
|
||||
timestamp = DateTime.MinValue;
|
||||
total_volume = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool get_last_trade(
|
||||
ref ulong time,
|
||||
out NativePriceSize ps,
|
||||
out DateTime timestamp,
|
||||
out ulong total_volume,
|
||||
out bool from_spread
|
||||
) {
|
||||
ps = new NativePriceSize();
|
||||
timestamp = DateTime.MinValue;
|
||||
total_volume = 0;
|
||||
from_spread = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
public class PriceLevel {
|
||||
public double price;
|
||||
public uint qty;
|
||||
|
||||
public PriceLevel(double price_arg, uint qty_arg) {
|
||||
price = price_arg;
|
||||
qty = qty_arg;
|
||||
}
|
||||
|
||||
public static PriceLevel empty => new(double.NaN, 0);
|
||||
|
||||
public static IntPrice intprice_from_double(double d) =>
|
||||
double.IsNaN(d) ? IntPrice.NaN : new IntPrice((long)d, 0);
|
||||
|
||||
public NativePriceSize get_native_ps() =>
|
||||
new(intprice_from_double(price), qty);
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalMDDaemon {
|
||||
|
||||
public class BBO {
|
||||
public double bid_price = Double.NaN;
|
||||
public UInt32 bid_qty = 0;
|
||||
public double ask_price = Double.NaN;
|
||||
public UInt32 ask_qty = 0;
|
||||
public UInt32 exchange_id = 0;
|
||||
public BBO(uint exchange_id) :
|
||||
this(Double.NaN, 0, Double.NaN, 0, exchange_id) { }
|
||||
public BBO(double bid_price_, uint bid_qty_, double ask_price_, uint ask_qty_, uint exchange_id_) {
|
||||
bid_price = bid_qty_ == 0 ? Double.NaN : bid_price_;
|
||||
bid_qty = bid_qty_;
|
||||
ask_price = ask_qty_ == 0 ? Double.NaN : ask_price_;
|
||||
ask_qty = ask_qty_;
|
||||
exchange_id = exchange_id_;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return $"bid_price: {bid_price} bid_qty: {bid_qty} ask_price: {ask_price} ask_qty: {ask_qty} exchange_id: {exchange_id}";
|
||||
}
|
||||
}
|
||||
static Process producer_process = null;
|
||||
static void start_or_restart_producer_process(string exePath) {
|
||||
foreach (var process in Process.GetProcessesByName(Path.GetFileNameWithoutExtension(exePath))) {
|
||||
try {
|
||||
bts.Log.Gen.info($"Stopping existing producer process (PID: {process.Id})...");
|
||||
process.Kill();
|
||||
process.WaitForExit();
|
||||
} catch (Exception ex) {
|
||||
bts.Log.Gen.error($"Stopping existing producer process (PID: {process.Id})...ex: {ex.Message}");
|
||||
}
|
||||
}
|
||||
if (producer_process != null && !producer_process.HasExited) {
|
||||
bts.Log.Gen.info("Stopping existing producer process...");
|
||||
producer_process.Kill();
|
||||
producer_process.WaitForExit();
|
||||
producer_process.Dispose();
|
||||
}
|
||||
|
||||
bts.Log.Gen.info("Starting new producer process...");
|
||||
|
||||
producer_process = new Process();
|
||||
producer_process.StartInfo.FileName = exePath; // Path to the EXE
|
||||
producer_process.StartInfo.UseShellExecute = false;
|
||||
// producer_process.StartInfo.RedirectStandardOutput = true;
|
||||
// producer_process.StartInfo.RedirectStandardError = true;
|
||||
producer_process.Start();
|
||||
|
||||
bts.Log.Gen.info("Producer process started.");
|
||||
}
|
||||
public Action<LocalMDDaemon.BBO> on_bbo = (bbo) => { };
|
||||
|
||||
public LocalMDDaemon() {
|
||||
Log.Gen.info("LocalMDDaemon.cons called, starting the reading thread now");
|
||||
System.Threading.Thread pipe_read_thr = new Thread(_start_reading);
|
||||
pipe_read_thr.IsBackground = true;
|
||||
pipe_read_thr.Start();
|
||||
|
||||
}
|
||||
|
||||
private void _start_reading() {
|
||||
// TODO, fix this
|
||||
string exe_path = @"dist\producer_delayed\producer_delayed.exe";
|
||||
//@"dist\producer\producer.exe";
|
||||
|
||||
// delete pipe if already exists
|
||||
using NamedPipeClientStream old_pipe_client = new NamedPipeClientStream(".", "MyPipe", PipeDirection.In);
|
||||
try {
|
||||
old_pipe_client.Close();
|
||||
old_pipe_client.Dispose();
|
||||
} catch (Exception ex) {
|
||||
Log.Gen.error($"LocalMDDaemon._start_reading ex: {ex}");
|
||||
}
|
||||
start_or_restart_producer_process(exe_path);
|
||||
|
||||
// Connect to the named pipe
|
||||
using NamedPipeClientStream pipe_client = new NamedPipeClientStream(".", "MyPipe", PipeDirection.In);
|
||||
|
||||
System.Threading.Thread.Sleep(100);
|
||||
pipe_client.Connect();
|
||||
|
||||
bts.Log.Gen.info("Connected to pipe. Waiting for messages...");
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
int bytes_read = pipe_client.Read(buffer, 0, buffer.Length);
|
||||
|
||||
if (bytes_read == 0)// this means there was an error
|
||||
{
|
||||
System.Threading.Thread.Sleep(2);
|
||||
continue;
|
||||
}
|
||||
Func<double, bool> qty_ok = (d) => {
|
||||
if (d < 0 || d > Convert.ToDouble(UInt32.MaxValue))
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
double double1 = BitConverter.ToDouble(buffer, 0);
|
||||
double double2 = BitConverter.ToDouble(buffer, 8);
|
||||
double double3 = BitConverter.ToDouble(buffer, 16);
|
||||
double double4 = BitConverter.ToDouble(buffer, 24);
|
||||
uint int5 = BitConverter.ToUInt32(buffer, 32);
|
||||
//Log.Gen.info($"Read from pipe {double1} , {double2} , {double3} , {double4} , {int5}");
|
||||
if (!qty_ok(double2) || !qty_ok(double4))
|
||||
continue;
|
||||
LocalMDDaemon.BBO bbo = new BBO(double1, Convert.ToUInt32(double2),
|
||||
double3, Convert.ToUInt32(double4), int5);
|
||||
on_bbo(bbo);
|
||||
} catch (IOException) {
|
||||
bts.Log.Gen.error("Pipe disconnected. Attempting to reconnect...");
|
||||
pipe_client.Dispose();
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
pipe_client.Connect(); // Try reconnecting
|
||||
} catch (Exception ex) {
|
||||
bts.Log.Gen.error($"Exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using bts;
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Text;
|
||||
|
||||
class LocalMDDaemon2 {
|
||||
static Process producerProcess = null;
|
||||
static void StartOrRestartProducerProcess(string exePath) {
|
||||
// foreach (var process in Process.GetProcessesByName(Path.GetFileNameWithoutExtension(exePath)))
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// Console.WriteLine($"Stopping existing producer process (PID: {process.Id})...");
|
||||
// process.Kill();
|
||||
// process.WaitForExit();
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Console.WriteLine($"Error stopping process: {ex.Message}");
|
||||
// }
|
||||
// }
|
||||
// if (producerProcess != null && !producerProcess.HasExited)
|
||||
// {
|
||||
// Console.WriteLine("Stopping existing producer process...");
|
||||
// producerProcess.Kill();
|
||||
// producerProcess.WaitForExit();
|
||||
// producerProcess.Dispose();
|
||||
// }
|
||||
|
||||
Console.WriteLine("Starting new producer process...");
|
||||
|
||||
producerProcess = new Process();
|
||||
producerProcess.StartInfo.FileName = exePath; // Path to the EXE
|
||||
producerProcess.StartInfo.UseShellExecute = false;
|
||||
// producerProcess.StartInfo.RedirectStandardOutput = true;
|
||||
// producerProcess.StartInfo.RedirectStandardError = true;
|
||||
producerProcess.Start();
|
||||
Console.WriteLine("Producer process started.");
|
||||
}
|
||||
|
||||
public static void start() {
|
||||
|
||||
// string exePath = @".\dist\producer\producer.exe"; //uncomment for live
|
||||
// string exePath = @".\dist\producer_delayed\producer_delayed.exe"; //UNCOMMENT FOR DELAYED
|
||||
string exePath = //@".\dist\producer_replay\producer_replay.exe"; //UNCOMMENT FOR replay
|
||||
@"C:\Users\m_tah\Downloads\HelloWorld\HelloWorld\dist\producer_replay\producer_replay.exe";
|
||||
|
||||
|
||||
// delete pipe if already exists
|
||||
// using NamedPipeClientStream oldpipeClient = new NamedPipeClientStream(".", "MyPipe", PipeDirection.In);
|
||||
// try{
|
||||
// oldpipeClient.Close();
|
||||
// oldpipeClient.Dispose();
|
||||
// }catch{
|
||||
|
||||
// }
|
||||
StartOrRestartProducerProcess(exePath);
|
||||
|
||||
// Connect to the named pipe
|
||||
using NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "MyPipe", PipeDirection.In);
|
||||
|
||||
// Thread.Sleep(2000);
|
||||
pipeClient.Connect();
|
||||
|
||||
Console.WriteLine("Connected to pipe. Waiting for messages...");
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
var start = true;
|
||||
while (true) {
|
||||
try {
|
||||
int bytesRead = pipeClient.Read(buffer, 0, buffer.Length);
|
||||
|
||||
if (bytesRead == 0)// this means there was an error
|
||||
{
|
||||
Console.WriteLine("0 bytes recieved ending...");
|
||||
if (start) {
|
||||
start = false;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
double double1 = BitConverter.ToDouble(buffer, 0);
|
||||
double double2 = BitConverter.ToDouble(buffer, 8);
|
||||
double double3 = BitConverter.ToDouble(buffer, 16);
|
||||
double double4 = BitConverter.ToDouble(buffer, 24);
|
||||
uint int5 = BitConverter.ToUInt32(buffer, 32);
|
||||
Log.Gen.info($"Received: {double1}, {double2}, {double3}, {double4}, {int5}");
|
||||
|
||||
} catch (IOException) {
|
||||
Console.WriteLine("Pipe disconnected. Attempting to reconnect...");
|
||||
pipeClient.Dispose();
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
pipeClient.Connect(); // Try reconnecting
|
||||
}
|
||||
}
|
||||
|
||||
// Console.WriteLine("Pipe closed. Exiting...");
|
||||
// producerProcess?.WaitForExit();
|
||||
}
|
||||
}
|
||||
@@ -133,16 +133,31 @@ namespace bts {
|
||||
|
||||
// set 'unit quantity' for each sec
|
||||
foreach (var smd in exch_secs.Values) {
|
||||
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);
|
||||
var (priceDisplay, qtyDisplay) = GetDisplaySettings(smd.security);
|
||||
exch_secs[smd.id] = new(smd.security, smd.md, priceDisplay, qtyDisplay);
|
||||
}
|
||||
Log.Gen.info($"MDDaemon.initialize posting sec, n: {exch_secs.Count}");
|
||||
_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);
|
||||
}
|
||||
}
|
||||
_bus.post(SparkTags.ServerSecuritiesTag, exch_secs);
|
||||
return (priceDisplay, qtyDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using BCG;
|
||||
|
||||
using bts.api;
|
||||
using bts.api.synthetic;
|
||||
using bts.collections;
|
||||
using bts.md;
|
||||
using bts.utils;
|
||||
|
||||
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
@@ -14,78 +11,103 @@ using mbts.ui;
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
using SecurityKey = System.UInt32;
|
||||
|
||||
namespace bts
|
||||
{
|
||||
namespace bts {
|
||||
|
||||
[Export(typeof(IShellDaemon))]
|
||||
internal class MergedMDDaemon : IShellDaemon, IDisposable
|
||||
{
|
||||
internal class MergedMDDaemon : IShellDaemon, IDisposable {
|
||||
private readonly IBus _bus;
|
||||
private LocalMDDaemon _local_md_daemon;
|
||||
IDisposable _dispose;
|
||||
Dictionary<uint, SecurityMD> _server_sec_map = new Dictionary<uint, SecurityMD>();
|
||||
ConcurrentDictionary<uint, SecurityMD> _server_sec_map = new ConcurrentDictionary<uint, SecurityMD>();
|
||||
Dictionary<uint, SecurityMD> _fake_sec_map = new Dictionary<uint, SecurityMD>();
|
||||
private ConcurrentDictionary<uint, SecurityKey> _exchange_id_sec_id_map = new ConcurrentDictionary<uint, SecurityKey>();
|
||||
private readonly IList<IDisposable> _subs = new List<IDisposable>();
|
||||
private SyncQueue<LocalMDDaemon.BBO> _waiting_bbo = new SyncQueue<LocalMDDaemon.BBO>();
|
||||
private readonly Venue _local_md_exchange = Venue.Cboe;
|
||||
private bool _localmd_daemon_inited = false;
|
||||
|
||||
private void _init_local_md_daemon() {
|
||||
if (_localmd_daemon_inited)
|
||||
return;
|
||||
//LocalMDDaemon2.start();
|
||||
_localmd_daemon_inited = true;
|
||||
_local_md_daemon = new LocalMDDaemon();
|
||||
_local_md_daemon.on_bbo += (bbo_pack) => {
|
||||
//bts.Log.Gen.info($"MergedMDDaemon on_bbo, local bbo callback, bbo: {bbo_pack}");
|
||||
_waiting_bbo.enqueue(bbo_pack);
|
||||
};
|
||||
System.Threading.Thread dequeue_bbo_thr = new Thread(_dequeue_bbo_thread_func);
|
||||
dequeue_bbo_thr.IsBackground = true;
|
||||
dequeue_bbo_thr.Priority = ThreadPriority.Normal;
|
||||
dequeue_bbo_thr.Start();
|
||||
}
|
||||
|
||||
[ImportingConstructor]
|
||||
public MergedMDDaemon(IBus bus)
|
||||
{
|
||||
//TempTester.test_analytics();
|
||||
public MergedMDDaemon(IBus bus) {
|
||||
bts.Log.Gen.info("MergedMDDaemon started");
|
||||
_bus = bus;
|
||||
|
||||
_subs.Add(_bus.subscribe(SparkTags.ServerSecuritiesTag).OnDispatcher().Subscribe(
|
||||
d => _on_server_sec_map(d)));
|
||||
_subs.Add(_bus.subscribe(SparkTags.FakeSecuritiesTag).OnDispatcher().Subscribe(
|
||||
d => _on_fake_sec_map(d)));
|
||||
// This is done periodically to remove expired contracts
|
||||
// (disabling it right now because we are not sure if deleting securities
|
||||
// works with the various UI windows)
|
||||
// _subs.Add(_bus.subscribe(SparkTags.SparkTimer).BufferLatest(TimeSpan.FromMinutes(1))
|
||||
// .Subscribe(t => _merge_and_post()));
|
||||
//_test_md_stream();
|
||||
}
|
||||
// BCG.MDStream _md_stream = null;
|
||||
// private void _test_md_stream()
|
||||
// {
|
||||
// _bus.subscribe(SparkTags.SparkTimer).Subscribe(t =>
|
||||
// {
|
||||
// if (_md_stream == null)
|
||||
// {
|
||||
// _md_stream = new BCG.MDStream();
|
||||
// _md_stream.onChange += _on_md;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// BCG.ViewStreamDatabase.instance.doWork();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
private void _on_md(object obj)
|
||||
{
|
||||
BCG.DataPacket dp = obj as DataPacket;
|
||||
bts.Log.Gen.info($"MDStream callback dp: {dp}");
|
||||
}
|
||||
|
||||
private void _log_options(IReadOnlyDictionary<uint, SecurityMD> sec_map)
|
||||
{
|
||||
private void _dequeue_bbo_thread_func() {
|
||||
while (true) {
|
||||
System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(0.1));
|
||||
_process_local_bbo_updates();
|
||||
}
|
||||
}
|
||||
|
||||
private void _process_local_bbo_updates() {
|
||||
if (_waiting_bbo.getCount() == 0)
|
||||
return;
|
||||
var bbo_list = _waiting_bbo.dequeue_all();
|
||||
// if (bbo_list.Count > -1)
|
||||
// return;
|
||||
if (bbo_list.Count >= 10) {
|
||||
Log.Gen.warn($"MergedMDDaemon._process_local_bbo_updates dequeued item count: {bbo_list.Count}");
|
||||
}
|
||||
List<LocalMDDaemon.BBO> unprocessed_bbo_list = new List<LocalMDDaemon.BBO>();
|
||||
foreach (var bbo in bbo_list) {
|
||||
bool processed = false;
|
||||
//bts.Log.Gen.info($"MergedMDDaemon._process_local_bbo_updates read from queue bbo: {bbo}");
|
||||
UInt32 exchange_id = bbo.exchange_id;
|
||||
SecurityKey sec_key;
|
||||
if (_exchange_id_sec_id_map.TryGetValue(exchange_id, out sec_key)) {
|
||||
SecurityMD sec_md;
|
||||
if (_server_sec_map.TryGetValue(sec_key, out sec_md)) {
|
||||
sec_md.md.set_bbo(bbo.bid_price, bbo.bid_qty, bbo.ask_price, bbo.ask_qty);
|
||||
processed = true;
|
||||
}
|
||||
}
|
||||
if (!processed) {
|
||||
// TODO: put it back in the queue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void _log_options(IReadOnlyDictionary<uint, SecurityMD> sec_map) {
|
||||
string rv = "MergedMDDaemon Options:\n";
|
||||
foreach (var kv in sec_map)
|
||||
{
|
||||
foreach (var kv in sec_map) {
|
||||
var sec = kv.Value.security;
|
||||
var md = kv.Value.md;
|
||||
ulong time = 0;
|
||||
NativePriceSize bid, ask;
|
||||
var has_md = md.get_bbo(ref time, out bid, out ask);
|
||||
if (sec.HasOption)
|
||||
{
|
||||
if (sec.HasOption) {
|
||||
rv += $"id: {kv.Key} desc: {sec.security_desc()} sym: {sec.Symbol} " +
|
||||
$"exp: {sec.security_expiration()} " +
|
||||
$"k: {sec.Option.Strike} cp: {sec.Option.CallPut} " +
|
||||
@@ -96,31 +118,40 @@ namespace bts
|
||||
Log.Gen.info(rv);
|
||||
}
|
||||
|
||||
private void _on_server_sec_map(IReadOnlyDictionary<uint, SecurityMD> sec_map)
|
||||
{
|
||||
private void _on_server_sec_map(IReadOnlyDictionary<uint, SecurityMD> sec_map) {
|
||||
//_log_options(sec_map);
|
||||
bts.Log.Gen.info(String.Format("MergedMDDaemon._on_server_sec_map sz: {0}", sec_map.Count()));
|
||||
_server_sec_map = new Dictionary<uint, SecurityMD>(sec_map);
|
||||
foreach (var kv in sec_map) {
|
||||
SecurityMD md = kv.Value;
|
||||
uint sec_id = kv.Key;
|
||||
SecurityMD sec_md = kv.Value;
|
||||
if (kv.Value.security.Exchange == _local_md_exchange) {
|
||||
UInt32 exchange_sec_id = Convert.ToUInt32(kv.Value.security.Cboe.ExchangeId);
|
||||
_exchange_id_sec_id_map[exchange_sec_id] = sec_id;
|
||||
_server_sec_map[sec_id] = new SecurityMD(sec_md.security, new LocalMDConsumerHandle(sec_md.security, new LocalMDDaemon.BBO(exchange_sec_id)));
|
||||
} else {
|
||||
_server_sec_map[sec_id] = sec_md;
|
||||
}
|
||||
}
|
||||
_merge_and_post();
|
||||
_init_local_md_daemon();
|
||||
}
|
||||
|
||||
private void _on_fake_sec_map(IReadOnlyDictionary<uint, SecurityMD> sec_map)
|
||||
{
|
||||
private void _on_fake_sec_map(IReadOnlyDictionary<uint, SecurityMD> sec_map) {
|
||||
bts.Log.Gen.info(String.Format("MergedMDDaemon._on_fake_sec_map sz: {0}", sec_map.Count()));
|
||||
_fake_sec_map = new Dictionary<uint, SecurityMD>(sec_map);
|
||||
_merge_and_post();
|
||||
}
|
||||
|
||||
private void _merge_and_post()
|
||||
{
|
||||
Func<SecurityMD, bool> is_active = (sec_md) =>
|
||||
{
|
||||
private void _merge_and_post() {
|
||||
Func<SecurityMD, bool> is_active = (sec_md) => {
|
||||
var sec = sec_md.security;
|
||||
return SecurityExpirationHelper.is_unexpired(sec);
|
||||
};
|
||||
// server secs
|
||||
var to_pub = _server_sec_map.Where(kv => is_active(kv.Value)).ToDictionary(kv => kv.Key, kv => kv.Value);
|
||||
foreach (var kv in _fake_sec_map)
|
||||
{
|
||||
// add fake secs to the map
|
||||
foreach (var kv in _fake_sec_map) {
|
||||
to_pub[kv.Key] = kv.Value;
|
||||
}
|
||||
bts.Log.Gen.info(String.Format("MergedMDDaemon._merge_and_post sz: {0}", to_pub.Count()));
|
||||
@@ -132,7 +163,9 @@ namespace bts
|
||||
_bus.post(SparkTags.TC2Tags.ExchangeSecurityMapTag, exs_map);
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
public void Dispose() {
|
||||
Disposable.dispose(ref _dispose);
|
||||
Log.Gen.info("LocalMDDaemon.DIspose called collecting resources now");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,13 @@ namespace bts.shell.market_view {
|
||||
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; }
|
||||
|
||||
@@ -128,6 +133,7 @@ namespace bts.shell.market_view {
|
||||
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());
|
||||
|
||||
|
||||
@@ -98,7 +98,14 @@ 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 =new RegexFilterVM<Row>(r => r.currency, nameof(Row.currency));
|
||||
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);
|
||||
// rows
|
||||
_wrapped_rows = _rows.WrapSelector(r => r.security_md, s => {
|
||||
var r = new Row(_bus, s, _orders_model, _position_model);
|
||||
@@ -124,7 +131,7 @@ namespace mbts.spark {
|
||||
|
||||
add_col(ColKey.Venue, r => r.venue.ToString(), nameof(Row.venue), venue_filter, FilterType.DiscreteValues, ColWidth.Exchange);
|
||||
add_col(ColKey.Security, r => r.security.ToString(), nameof(Row.security), security_filter, FilterType.Regex, ColWidth.Security);
|
||||
add_col(ColKey.Currency, r => r.currency, nameof(Row.currency), currency_filter, FilterType.Regex, ColWidth.Currency);
|
||||
add_col(ColKey.Currency, r => r.currency, nameof(Row.currency), currency_filter, FilterType.DiscreteValues, ColWidth.Currency);
|
||||
add_col(ColKey.Volume, r => r.volume_text, nameof(Row.volume), volume_filter, FilterType.ULongMinMax, ColWidth.Volume);
|
||||
|
||||
// last
|
||||
@@ -391,7 +398,7 @@ namespace mbts.spark {
|
||||
|
||||
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));
|
||||
@@ -538,6 +545,7 @@ 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(),
|
||||
@@ -584,6 +592,7 @@ 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,6 +237,17 @@ namespace mbts.spark {
|
||||
DefaultColumnWidths.Qty,
|
||||
get_fg: r => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
|
||||
|
||||
add_col(
|
||||
Columns.NotionalValue,
|
||||
DefaultColumnHeaders.NotionalValue,
|
||||
r => _get_row(r).security.spec_or_default().format(r.fill.notional_value()),
|
||||
new DoubleMinMax<FillTuple>(r => r.fill.notional_value()),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
fs => fs.notional_value,
|
||||
(fs, cf) => fs.notional_value = cf,
|
||||
DefaultColumnWidths.NotionalValue,
|
||||
get_fg: r => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
|
||||
|
||||
add_col(
|
||||
Columns.Price,
|
||||
DefaultColumnHeaders.Price,
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace mbts.ui.spark {
|
||||
Side,
|
||||
Price,
|
||||
Size,
|
||||
NotionalValue,
|
||||
FillKind,
|
||||
FillStatus,
|
||||
FillKey,
|
||||
|
||||
@@ -61,6 +61,10 @@ namespace bts.shell.fills {
|
||||
[DIYGridColumnID(nameof(Columns.Size))]
|
||||
public DoubleMinMaxState size { get; set; }
|
||||
|
||||
[ProtoMember(18)]
|
||||
[DIYGridColumnID(nameof(Columns.NotionalValue))]
|
||||
public DoubleMinMaxState notional_value { get; set; }
|
||||
|
||||
[ProtoMember(11)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKey))]
|
||||
public string fill_key { get; set; }
|
||||
@@ -113,6 +117,7 @@ namespace bts.shell.fills {
|
||||
pb.add_discrete_values(fs.fill_status, ft => ft.fill.FillStatus);
|
||||
// numeric
|
||||
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
|
||||
pb.add_numeric(fs.notional_value, ft => ft.fill.notional_value());
|
||||
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
|
||||
|
||||
if (fs.exp_date.HasValue) {
|
||||
|
||||
@@ -408,18 +408,18 @@ namespace mbts.spark {
|
||||
|
||||
[ProtoContract]
|
||||
class LadderPanelPricePointSettings : IEquatable<LadderPanelPricePointSettings> {
|
||||
public static LadderPanelPricePointSettings historic_default =>
|
||||
public static LadderPanelPricePointSettings Default =>
|
||||
new LadderPanelPricePointSettings {
|
||||
day_change = new DaySettings {
|
||||
// (neutral)
|
||||
font = _default_font,
|
||||
background_color = Colors.Transparent.to_uint(),
|
||||
font = _default_font.With(font_color: Colors.Black.to_uint()),
|
||||
background_color = Color.FromRgb(113, 201, 69).to_uint(),
|
||||
// negative
|
||||
negative_font_color = Colors.White.to_uint(),
|
||||
negative_background_color = Colors.Red.to_uint(),
|
||||
// positive
|
||||
positive_font_color = Colors.White.to_uint(),
|
||||
positive_background_color = Colors.Green.to_uint()
|
||||
positive_background_color = Colors.Green.to_uint(),
|
||||
},
|
||||
session_low = _default_item.With(_default_item.font.With(font_color: BTSColors.MarketData.session_low)),
|
||||
session_high = _default_item.With(_default_item.font.With(font_color: BTSColors.MarketData.session_high)),
|
||||
@@ -431,21 +431,9 @@ namespace mbts.spark {
|
||||
theo = _default_item,
|
||||
delta = _default_item,
|
||||
vega = _default_item,
|
||||
volume = _default_item
|
||||
volume = _default_item,
|
||||
};
|
||||
|
||||
public static LadderPanelPricePointSettings tt_default {
|
||||
get {
|
||||
var rv = historic_default;
|
||||
rv.day_change.background_color = Color.FromRgb(113, 201, 69).to_uint();
|
||||
rv.day_change.font.font_color = Colors.Black.to_uint();
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
public static LadderPanelPricePointSettings Default => LadderSettings.mimic_tt ?
|
||||
tt_default : historic_default;
|
||||
|
||||
[ProtoMember(1)]
|
||||
public DaySettings day_change { get; set; }
|
||||
[ProtoMember(2)]
|
||||
@@ -607,11 +595,6 @@ namespace mbts.spark {
|
||||
uint _ticks_per_row;
|
||||
List<FairValueLine> _fair_value_lines;
|
||||
|
||||
static LadderSettings() {
|
||||
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
|
||||
mimic_tt = knobs.MimicTt;
|
||||
}
|
||||
|
||||
// Default proto constructor
|
||||
// Added this so that when clients upgrade to a version with new settings,
|
||||
// the default values are applied correctly; thx to John for this tip
|
||||
@@ -643,9 +626,7 @@ namespace mbts.spark {
|
||||
status_message_visible = true;
|
||||
}
|
||||
|
||||
public static bool mimic_tt { get; }
|
||||
|
||||
public static LadderSettings historic_default {
|
||||
public static LadderSettings Default {
|
||||
get {
|
||||
LadderColumnSettings
|
||||
orders_column = LadderColumnSettings.default_orders,
|
||||
@@ -713,11 +694,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static LadderSettings tt_default => historic_default;
|
||||
|
||||
public static LadderSettings Default => mimic_tt ?
|
||||
tt_default : historic_default;
|
||||
|
||||
public enum BadgeStyle { Normal, Alternate }
|
||||
|
||||
// TODO: rename to PriceDisplayFormat (today is merge monday, since this isn't a functional change, it is not worth the potential merge headaches right now)
|
||||
@@ -1039,7 +1015,6 @@ namespace mbts.spark {
|
||||
a.ladder_order_limits.safe_equals(b.ladder_order_limits) &&
|
||||
a._ticks_per_row == b._ticks_per_row &&
|
||||
a.panel_price_point_settings.safe_equals(b.panel_price_point_settings) &&
|
||||
a._ticks_per_row == b._ticks_per_row &&
|
||||
EqualityComparer.safe_sequence_equals(a.fair_value_lines, b.fair_value_lines);
|
||||
|
||||
public bool Equals(LadderSettings other) =>
|
||||
|
||||
@@ -107,6 +107,7 @@ namespace mbts.spark {
|
||||
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoMetadata> _favorite_algo_metadata_map =
|
||||
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoMetadata>.Empty;
|
||||
RegionalTickFactors? _spec;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>(0);
|
||||
|
||||
LadderVM(
|
||||
IBus bus,
|
||||
@@ -329,7 +330,8 @@ namespace mbts.spark {
|
||||
}),
|
||||
WorkLoop.Start($"Ladder '{security}'", _tick),
|
||||
ObservableAutoWrapper.dispose_on_removed(orders, true),
|
||||
Disposable.Create(() => _on_disposed_impl?.Invoke(true)));
|
||||
Disposable.Create(() => _on_disposed_impl?.Invoke(true)),
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(secs => _securities = secs));
|
||||
|
||||
PropertyAutoWrapper<LadderSettings, bool> bool_prop(
|
||||
string name, Func<LadderSettings, bool> get_bool) =>
|
||||
@@ -405,7 +407,9 @@ namespace mbts.spark {
|
||||
event Action<bool>? _on_disposed_impl;
|
||||
|
||||
public IMDConsumerHandle md => _model.md;
|
||||
public SecurityMD security_md => _model.security_md;
|
||||
public IBus bus => _bus;
|
||||
public IReadOnlyDictionary<uint, SecurityMD> securities => _securities;
|
||||
|
||||
// we return only entries who have piqs and whose piqs are not uint.Max
|
||||
// uint.Max happens when the server is restarted but the client is still up
|
||||
|
||||
@@ -17,7 +17,6 @@ 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;
|
||||
@@ -38,9 +37,6 @@ namespace mbts.spark {
|
||||
partial class LadderGridHost : Control {
|
||||
static readonly int _initial_row_count;
|
||||
static readonly bool _show_scrollbar;
|
||||
static readonly bool _lighten_market_cells;
|
||||
static readonly Color? _bid_market_cell_background;
|
||||
static readonly Color? _ask_market_cell_background;
|
||||
|
||||
readonly VisualHost _visual_host = new();
|
||||
readonly LadderOrdersHost _ordershost;
|
||||
@@ -84,6 +80,7 @@ namespace mbts.spark {
|
||||
LadderTactileAdorner _tactile_adorner;
|
||||
ulong _prices_updated;
|
||||
NativePriceSize[] _bids = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH], _asks = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
|
||||
SecurityMDExtensions.Book _depth = SecurityMDExtensions.Book.Empty;
|
||||
bool _price_dirty = false, _piq_dirty = false;
|
||||
LadderPriceList _price_list = new() {
|
||||
length = _initial_row_count,
|
||||
@@ -96,6 +93,8 @@ namespace mbts.spark {
|
||||
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);
|
||||
int _old_tooltip_row = int.MaxValue;
|
||||
NativePriceSize? _old_last_trade;
|
||||
string _direction_arrow = string.Empty;
|
||||
@@ -107,17 +106,7 @@ namespace mbts.spark {
|
||||
static LadderGridHost() {
|
||||
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
|
||||
_show_scrollbar = knobs.ShowScrollbar;
|
||||
_lighten_market_cells = knobs.LightenMarketCells;
|
||||
_initial_row_count = (int)knobs.ScrollableTickWindowSize;
|
||||
// For market cell backgrounds, 0 means null. Note that the full
|
||||
// range of colors can still be expressed, as zero is merely one of
|
||||
// 2^24 possible representations of 'transparent.'
|
||||
if (knobs.BidMarketCellBackground != 0) {
|
||||
_bid_market_cell_background = BTSColors.Ladder.bid_column_background;
|
||||
}
|
||||
if (knobs.AskMarketCellBackground != 0) {
|
||||
_ask_market_cell_background = BTSColors.Ladder.ask_column_background;
|
||||
}
|
||||
}
|
||||
|
||||
public LadderGridHost() {
|
||||
@@ -872,13 +861,13 @@ namespace mbts.spark {
|
||||
switch (column.column_type) {
|
||||
case ColumnType.Order: _ordershost.set_col_width(column.settings.width); break;
|
||||
case ColumnType.Bid:
|
||||
render_market_col(_bids, _bid_market_cell_background);
|
||||
render_market_col(_depth.Bids);
|
||||
break;
|
||||
case ColumnType.Price:
|
||||
_refresh_prices(column);
|
||||
break;
|
||||
case ColumnType.Ask:
|
||||
render_market_col(_asks, _ask_market_cell_background);
|
||||
render_market_col(_depth.Asks);
|
||||
break;
|
||||
case ColumnType.Volume:
|
||||
_volume_container.mark_dirty();
|
||||
@@ -891,9 +880,9 @@ namespace mbts.spark {
|
||||
if (_mkt_break_lines.high_render_index.HasValue)
|
||||
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index.Value, _settings.session_high_line.color);
|
||||
}
|
||||
void render_market_col(NativePriceSize[] depth, Color? market_cell_background) {
|
||||
void render_market_col(IEnumerable<(double Price, double Volume, double NotionalValue)> depth) {
|
||||
var mid = LadderRender.index_of(_price_list, _last_origin_price, _price_comparer);
|
||||
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_mkt_depth_item, _lighten_market_cells, market_cell_background);
|
||||
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_price_level);
|
||||
}
|
||||
}
|
||||
_apply_column_visual_offset();
|
||||
@@ -939,6 +928,7 @@ namespace mbts.spark {
|
||||
// **use local vm to protect against re-assignment,
|
||||
// so the events are unhooked from the same instance to which they were added
|
||||
// when the subs are disposed.
|
||||
Disposable.dispose(ref _vm_sub);
|
||||
var local_vm = _vm;
|
||||
local_vm.on_tick += _tick;
|
||||
local_vm.request_recenter += on_request_recenter;
|
||||
@@ -1110,6 +1100,7 @@ namespace mbts.spark {
|
||||
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);
|
||||
@@ -1322,8 +1313,8 @@ namespace mbts.spark {
|
||||
void _refresh_sizes() {
|
||||
Log.assert_d(_price_list != null);
|
||||
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
|
||||
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, _spec, _settings.bid_column, _fmt_mkt_depth_item, _lighten_market_cells, _bid_market_cell_background);
|
||||
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, _spec, _settings.ask_column, _fmt_mkt_depth_item, _lighten_market_cells, _ask_market_cell_background);
|
||||
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _depth.Bids, _settings.row_height, _spec, _settings.bid_column, _fmt_price_level);
|
||||
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _depth.Asks, _settings.row_height, _spec, _settings.ask_column, _fmt_price_level);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1506,11 +1497,13 @@ namespace mbts.spark {
|
||||
static GlyphContext glyph_context(LadderColumnSettings cs) =>
|
||||
GlyphContext.create(cs.font_family, cs.font_weight);
|
||||
|
||||
var price_display = _spec.tick_factors(0).to_api_tick_display_settingsi();
|
||||
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);
|
||||
_set_columns();
|
||||
_apply_column_visual_offset();
|
||||
if (prev_row_height != _settings.row_height)
|
||||
@@ -1520,18 +1513,16 @@ namespace mbts.spark {
|
||||
_mkt_break_lines.fair_value_line_views.Clear();
|
||||
// Clear and re-add the columns to the viewer to ensure offset/visibility is correct
|
||||
_visual_host.Clear();
|
||||
if (LadderSettings.mimic_tt) {
|
||||
// The session_range_vis sits beneath the grid backgrounds (on the
|
||||
// z-axis) to avoid obfuscating the font color (dj wants it to be
|
||||
// pure white). Historically, it sat atop the price column, with a
|
||||
// low opacity to slightly darken the range of prices inside of the
|
||||
// session range (i.e., the price-band of the day's trades). In
|
||||
// conjuction with this change, the price column's background
|
||||
// opacity is made slightly less than 1 (in the default settings),
|
||||
// while the font color remains fully opaque, so this layer will
|
||||
// darken the background without affecting the font color.
|
||||
_visual_host.AddVisualChild(_session_range_vis);
|
||||
}
|
||||
// The session_range_vis sits beneath the grid backgrounds (on the
|
||||
// z-axis) to avoid obfuscating the font color (dj wants it to be
|
||||
// pure white). Historically, it sat atop the price column, with a
|
||||
// low opacity to slightly darken the range of prices inside of the
|
||||
// session range (i.e., the price-band of the day's trades). In
|
||||
// conjuction with this change, the price column's background
|
||||
// opacity is made slightly less than 1 (in the default settings),
|
||||
// while the font color remains fully opaque, so this layer will
|
||||
// darken the background without affecting the font color.
|
||||
_visual_host.AddVisualChild(_session_range_vis);
|
||||
// Renders the grid lines and column backgrounds
|
||||
_visual_host.AddVisualChild(_gridvis);
|
||||
// Orders column
|
||||
@@ -1571,8 +1562,6 @@ namespace mbts.spark {
|
||||
with_vis => _visual_host.RemoveVisualChild(with_vis.vis));
|
||||
_visual_host.AddVisualChild(_session_low_vis);
|
||||
_visual_host.AddVisualChild(_session_high_vis);
|
||||
if (!LadderSettings.mimic_tt)
|
||||
_visual_host.AddVisualChild(_session_range_vis);
|
||||
_render_grid_background();
|
||||
_init_tactile_adorner();
|
||||
// this effectively sets the background of the "Last" column //deleted to fix color
|
||||
@@ -1594,6 +1583,16 @@ namespace mbts.spark {
|
||||
return impl(value);
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
|
||||
string _fmt_price_level(LadderPriceLevel value) {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_price_level_impl;
|
||||
impl ??= MDDisplayHelper.get_formatter_lpl(
|
||||
MarketSizeDisplay.get_default(), _qty_display, security.regional_ticks().tick_factors(0).to_api_tick_display_settingsi());
|
||||
return impl(value);
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_quantity_impl.
|
||||
string _fmt_mkt_quantity(double value, double price) {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
namespace mbts.spark;
|
||||
|
||||
using bts;
|
||||
using bts.utils;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
readonly struct LadderPriceLevel : IEquatable<LadderPriceLevel> {
|
||||
public LadderPriceLevel(double price = double.NaN, double volume = 0, double notionalValue = double.NaN) {
|
||||
Price = price;
|
||||
Volume = volume;
|
||||
NotionalValue = notionalValue.is_nan() ? volume * price : notionalValue;
|
||||
}
|
||||
|
||||
public static LadderPriceLevel Empty => new();
|
||||
|
||||
public double Price { get; }
|
||||
public double Volume { get; }
|
||||
public double NotionalValue { get; }
|
||||
|
||||
public LadderPriceLevel Add(double price, double volume) {
|
||||
if (!(volume > 0)) {
|
||||
return this;
|
||||
}
|
||||
if (!(Volume > 0)) {
|
||||
return new(price, volume);
|
||||
}
|
||||
Log.assert(Price.is_real());
|
||||
Log.assert(price.is_real());
|
||||
var newVolume = Volume + volume;
|
||||
var newPrice = (Price * Volume + price * volume) / newVolume;
|
||||
return new(newPrice, newVolume);
|
||||
}
|
||||
|
||||
public LadderPriceLevel Add(IEnumerable<(double Price, double Volume)> items) {
|
||||
var (totalPrice, totalVolume) = items.Sum();
|
||||
return totalVolume > 0
|
||||
? Add(totalPrice / totalVolume, totalVolume)
|
||||
: this;
|
||||
}
|
||||
|
||||
public Builder ToBuilder() =>
|
||||
new() { Cost = Price, Volume = Volume };
|
||||
|
||||
public LadderPriceLevel With(double price = double.NaN, double volume = double.NaN, double notionalValue = double.NaN) =>
|
||||
new(price.if_nan(Price), volume.if_nan(Volume), notionalValue.if_nan(NotionalValue));
|
||||
|
||||
public bool Equals(LadderPriceLevel other) =>
|
||||
MathUtils.epsilon_eq_comparer.Equals(Price, other.Price)
|
||||
&& MathUtils.epsilon_eq_comparer.Equals(Volume, other.Volume);
|
||||
|
||||
public override bool Equals([NotNullWhen(true)] object obj) =>
|
||||
obj is LadderPriceLevel other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(
|
||||
MathUtils.epsilon_eq_comparer.GetHashCode(Price),
|
||||
MathUtils.epsilon_eq_comparer.GetHashCode(Volume));
|
||||
|
||||
public class Builder {
|
||||
double notionalValue = double.NaN;
|
||||
|
||||
public double Cost { get; set; } = double.NaN;
|
||||
public double Volume { get; set; } = 0;
|
||||
public double NotionalValue => notionalValue.is_nan() ? Volume * Cost : notionalValue;
|
||||
|
||||
public void Add(double price, double volume) {
|
||||
if (!(volume > 0)) {
|
||||
return;
|
||||
}
|
||||
Log.assert(price.is_real());
|
||||
Cost += price * volume;
|
||||
Volume += volume;
|
||||
}
|
||||
|
||||
public void Add(double price, double volume, double notionalValue) {
|
||||
if (!(volume > 0)) {
|
||||
return;
|
||||
}
|
||||
Log.assert(price.is_real());
|
||||
Cost += price * volume;
|
||||
Volume += volume;
|
||||
if (this.notionalValue.is_nan()) {
|
||||
this.notionalValue = notionalValue;
|
||||
} else{
|
||||
this.notionalValue += notionalValue;
|
||||
}
|
||||
}
|
||||
|
||||
public LadderPriceLevel ToImmutable() =>
|
||||
Volume > 0
|
||||
? new(Cost / Volume, Volume, NotionalValue)
|
||||
: Empty;
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,7 @@ namespace mbts.spark {
|
||||
static class LadderRender {
|
||||
// Note: the orders-column has a separate "row_padding" value: Badge.DefaultMargin
|
||||
// (see how it is incorporated in measure_row_height() below)
|
||||
public static Thickness row_padding => LadderSettings.mimic_tt ?
|
||||
new Thickness(4, 2, 4, 2) : new Thickness(6);
|
||||
public static Thickness row_padding => new Thickness(4, 2, 4, 2);
|
||||
|
||||
const string tick_separator_period = ".";
|
||||
const string tick_separator_dash = "-";
|
||||
@@ -214,70 +213,78 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static void RenderSizes(
|
||||
DrawingVisual dv,
|
||||
GlyphContext glyphContext,
|
||||
int originIndex,
|
||||
IList<double> prices,
|
||||
IEnumerable<LadderPriceLevel> levels,
|
||||
double rowHeight,
|
||||
RegionalTickFactors spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<LadderPriceLevel, string> fmt) {
|
||||
var bg = settings.background.rgb_color().blend((BTSColor)0x2AFFFFFF).to_brush();
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, rowHeight, prices.Count))
|
||||
using (var levelsEn = (levels ?? Enumerable.Empty<LadderPriceLevel>()).GetEnumerator()) {
|
||||
var fg = settings.foreground.rgb_brush();
|
||||
var currIdx = originIndex;
|
||||
var currPrice = prices[currIdx];
|
||||
while (levelsEn.MoveNext()) {
|
||||
var level = levelsEn.Current;
|
||||
var ticks = spec.count_ticks(level.Price);
|
||||
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : level.Price;
|
||||
// prices are in descending order, so get diff from curr_price - price
|
||||
var tickDiff = spec.tickdiff(currPrice, price).assert_value();
|
||||
if (!MathUtils.try_increment(ref currIdx, tickDiff) || currIdx < 0 || currIdx >= prices.Count) {
|
||||
continue;
|
||||
}
|
||||
currPrice = price;
|
||||
if (bg is not null) {
|
||||
var y = currIdx * rowHeight + GRID_LINE_HEIGHT;
|
||||
var height = rowHeight - GRID_LINE_HEIGHT;
|
||||
dc.DrawRectangle(bg, null, new Rect(0, y, settings.width, height));
|
||||
}
|
||||
var gr = format_text(fmt(level), currIdx, rowHeight, settings, glyphContext);
|
||||
dc.DrawGlyphRun(fg, gr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<LadderPriceLevel> GetLevels(IEnumerable<(double Price, double Volume, double NotionalValue)> depth, RegionalTickFactors spec) =>
|
||||
depth.TakeWhile(nps => nps.Volume > 0 && nps.Price.is_real())
|
||||
.AggregateConsecutiveGroups(
|
||||
LadderPriceLevel.Empty.ToBuilder,
|
||||
(bd, rung) => {
|
||||
bd.Add(rung.Price, rung.Volume, rung.NotionalValue);
|
||||
return bd;
|
||||
},
|
||||
rung => {
|
||||
var ticks = spec.count_ticks(rung.Price);
|
||||
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.Price;
|
||||
})
|
||||
.Select(bd => bd.ToImmutable());
|
||||
|
||||
public static void render_sizes(
|
||||
DrawingVisual dv,
|
||||
GlyphContext glyph_context,
|
||||
int origin_index,
|
||||
IList<double> prices,
|
||||
NativePriceSize[] rungs,
|
||||
IEnumerable<(double Price, double Volume, double NotionalValue)> rungs,
|
||||
double row_height,
|
||||
RegionalTickFactors spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<NativePriceSize, string> fmt,
|
||||
bool lighten_market_cells,
|
||||
Color? market_cell_background) {
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
Brush market_cell_background_brush;
|
||||
if (market_cell_background.HasValue) {
|
||||
market_cell_background_brush = new SolidColorBrush(market_cell_background.Value);
|
||||
} else if (lighten_market_cells) {
|
||||
market_cell_background_brush = settings.background.rgb_color()
|
||||
.blend((BTSColor)0x2AFFFFFF)
|
||||
.to_brush();
|
||||
} else {
|
||||
market_cell_background_brush = null;
|
||||
}
|
||||
// market_cell_background_brush = settings.background.rgb_color().blend((BTSColor)0xFF000000).to_brush();
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, prices.Count * row_height));
|
||||
if (rungs.is_null_or_empty()) {
|
||||
return;
|
||||
}
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
int curr_index = origin_index;
|
||||
double curr_price = prices[curr_index];
|
||||
NativePriceSize curr_size = NativePriceSize.NO_MARKET;
|
||||
var row_tick_groups = rungs
|
||||
.TakeWhile(nps => !nps.is_nan)
|
||||
.AggregateConsecutiveGroups(() => NativePriceSize.NO_MARKET,
|
||||
(aggr, rung) => rung.with(
|
||||
direct_size: aggr.direct_size + rung.direct_size,
|
||||
implied_size: aggr.implied_size + rung.implied_size),
|
||||
rung => {
|
||||
var ticks = spec.count_ticks(rung.price);
|
||||
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
|
||||
});
|
||||
foreach (var rung in row_tick_groups) {
|
||||
//double price = spec.round_to_tick(rung.price);
|
||||
var ticks = spec.count_ticks(rung.price);
|
||||
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
|
||||
// prices are in descending order, so get diff from curr_price - price
|
||||
var tick_diff = spec.tickdiff(curr_price, price).assert_value();
|
||||
if (!MathUtils.try_increment(ref curr_index, tick_diff)) {
|
||||
continue;
|
||||
}
|
||||
curr_price = price;
|
||||
if (curr_index >= 0 && curr_index < prices.Count) {
|
||||
if (market_cell_background_brush != null)
|
||||
dc.DrawRectangle(market_cell_background_brush, null,
|
||||
new Rect(0, curr_index * row_height + GRID_LINE_HEIGHT,
|
||||
settings.width, row_height - GRID_LINE_HEIGHT));
|
||||
var gr = format_text(fmt(rung), curr_index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(foreground, gr);
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
Func<LadderPriceLevel, string> fmt) {
|
||||
RenderSizes(
|
||||
dv,
|
||||
glyph_context,
|
||||
origin_index,
|
||||
prices,
|
||||
GetLevels(rungs, spec),
|
||||
row_height,
|
||||
spec,
|
||||
settings,
|
||||
fmt);
|
||||
}
|
||||
|
||||
public static void render_volumes(
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CancelAll" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Grid.Row" Value="2" />
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center">Cancel All</TextBlock>
|
||||
@@ -220,10 +220,7 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="TT_CancelAll" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
</Style>
|
||||
<Style x:Key="TT_CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Setter Property="Grid.Row" Value="1" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
@@ -231,7 +228,7 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="TT_CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Setter Property="Grid.Row" Value="2" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
@@ -239,83 +236,6 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Grid.Row" Value="0" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{Binding bid_foreground}">
|
||||
Cancel Buys
|
||||
</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{u:BTSColor UI.Button.normal_border}"
|
||||
Background="{Binding bid_background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
Name="button_content" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#99167ed3" />
|
||||
<Setter
|
||||
TargetName="Border"
|
||||
Property="BorderBrush"
|
||||
Value="{u:BTSColor UI.Button.mouseover_border}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}" />
|
||||
<Setter Property="Grid.Row" Value="1" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{Binding ask_foreground}">
|
||||
Cancel Sells
|
||||
</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{u:BTSColor UI.Button.normal_border}"
|
||||
Background="{Binding ask_background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True" >
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
Name="button_content" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
|
||||
<Setter
|
||||
TargetName="Border"
|
||||
Property="BorderBrush"
|
||||
Value="{u:BTSColor UI.Button.mouseover_border}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="SettingsButton" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
@@ -1177,19 +1097,19 @@
|
||||
<Button
|
||||
Command="{Binding cancel_cmd}"
|
||||
CommandParameter="Bids"
|
||||
Style="{StaticResource {x:Static sp:LadderView.CancelBuysButtonStyleKey}}"
|
||||
Style="{StaticResource CancelBuys}"
|
||||
Visibility="{Binding
|
||||
show_cancel_buys, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<Button
|
||||
Command="{Binding cancel_cmd}"
|
||||
CommandParameter="Asks"
|
||||
Style="{StaticResource {x:Static sp:LadderView.CancelSellsButtonStyleKey}}"
|
||||
Style="{StaticResource CancelSells}"
|
||||
Visibility="{Binding
|
||||
show_cancel_sells, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<Button
|
||||
Command="{Binding cancel_cmd}"
|
||||
CommandParameter="All"
|
||||
Style="{StaticResource {x:Static sp:LadderView.CancelAllButtonStyleKey}}"
|
||||
Style="{StaticResource CancelAll}"
|
||||
Visibility="{Binding
|
||||
show_cancel_all, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
|
||||
@@ -27,22 +27,6 @@ namespace mbts.spark {
|
||||
MinZoomFactor = 0.5,
|
||||
MaxZoomFactor = 1.5;
|
||||
|
||||
static LadderView() {
|
||||
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
|
||||
MimicTT = knobs.MimicTt;
|
||||
}
|
||||
|
||||
public static bool MimicTT { get; }
|
||||
|
||||
public static string CancelBuysButtonStyleKey =>
|
||||
MimicTT ? "TT_CancelBuys" : "CancelBuys";
|
||||
|
||||
public static string CancelSellsButtonStyleKey =>
|
||||
MimicTT ? "TT_CancelSells" : "CancelSells";
|
||||
|
||||
public static string CancelAllButtonStyleKey =>
|
||||
MimicTT ? "TT_CancelAll" : "CancelAll";
|
||||
|
||||
public static IMultiValueConverter PositionToolTipConverter =
|
||||
ValueConverter.create<AccountTuple?, bool, bool, bool, string>((account, show_day_pos, show_raw_day_pos, show_total_pos) => {
|
||||
const string day_pos_text = "day", raw_day_pos_text = "raw day", total_pos_text = "total";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using bts;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
@@ -223,6 +223,10 @@ namespace mbts.spark {
|
||||
Add(qty_column = new SizeColumn(
|
||||
new DoubleMinMax<OrderRecordTupleWithRoot>(
|
||||
ort => ort.order.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
// notional value
|
||||
Add(notional_value_column = new NotionalValueColumn(
|
||||
new DoubleMinMax<OrderRecordTupleWithRoot>(
|
||||
ort => ort.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
|
||||
@@ -232,6 +236,10 @@ 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,
|
||||
@@ -363,9 +371,11 @@ 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; }
|
||||
@@ -391,8 +401,10 @@ 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(),
|
||||
@@ -415,7 +427,9 @@ 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);
|
||||
@@ -645,6 +659,32 @@ 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)) { }
|
||||
@@ -664,6 +704,32 @@ 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,6 +108,8 @@ namespace mbts.spark {
|
||||
public double display_quantity => quantity.safe_divide(_display_qty_divisor);
|
||||
public string display_quantity_text =>
|
||||
quantity_display_settings.to_tick_display_settings().format(display_quantity);
|
||||
public double notional_value => order_tuple.notional_value;
|
||||
public string notional_value_text => _fmt_price(notional_value);
|
||||
public TickDisplaySettingsImmutable quantity_display_settings {
|
||||
get => _quantity_display_settings;
|
||||
set {
|
||||
@@ -128,6 +130,8 @@ 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;
|
||||
@@ -355,9 +359,15 @@ 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)
|
||||
@@ -521,6 +531,12 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,160 +1,170 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = EnumHelper.get_values<Columns>().ToDictionary(
|
||||
var col_settings_flags = Enum.GetValuesAsUnderlyingType<Columns>().Cast<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,270 +1,277 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +1,109 @@
|
||||
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() { }
|
||||
}
|
||||
}
|
||||
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() { }
|
||||
}
|
||||
}
|
||||
@@ -1,378 +1,380 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace mbts.spark {
|
||||
private readonly ThreadedBlackboard _bb;
|
||||
HashSet<BCG.NMSheetSymbol> _sym_map;
|
||||
private Action _cb = () => { };
|
||||
private readonly TimeSpan fire_period = TimeSpan.FromMilliseconds(300);
|
||||
private readonly TimeSpan fire_period = TimeSpan.FromMilliseconds(333);
|
||||
|
||||
public NMSheetBBODataSource(ThreadedBlackboard bb, List<BCG.NMSheetSymbol> syms, Action cb) {
|
||||
_bb = bb;
|
||||
|
||||
@@ -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) && theo < bid_price)
|
||||
if (!Double.IsNaN(theo) && !Double.IsNaN(bid_price) && (bid_price - theo) > _basic_crosss_eps)
|
||||
return new NMSheetTheoBBOCross(CrossState.bid_cross, 0);
|
||||
else if (!Double.IsNaN(theo) && !Double.IsNaN(ask_price) && theo > ask_price)
|
||||
else if (!Double.IsNaN(theo) && !Double.IsNaN(ask_price) && (theo - ask_price) > _basic_crosss_eps)
|
||||
return new NMSheetTheoBBOCross(CrossState.ask_cross, 0);
|
||||
else if (is_proximity_alert_enabled) {
|
||||
/// bool is passed to bypass doing the below
|
||||
@@ -87,6 +87,7 @@ 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; }
|
||||
|
||||
@@ -64,8 +64,7 @@ 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;
|
||||
}
|
||||
@@ -78,8 +77,7 @@ 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;
|
||||
}
|
||||
@@ -165,6 +163,13 @@ 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;
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace mbts.analytics {
|
||||
return now_asset_value - init_asset_value;
|
||||
}
|
||||
|
||||
|
||||
public double driver {
|
||||
get {
|
||||
if (tl_row.security_type == SecurityType.Option) {
|
||||
@@ -44,13 +45,18 @@ namespace mbts.analytics {
|
||||
}
|
||||
public double theo {
|
||||
get {
|
||||
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;
|
||||
switch (tl_row.security_type) {
|
||||
case SecurityType.Option:
|
||||
return tl_row.call_put == PutOrCall.Call ?
|
||||
opt_theo.fwd_normalized_call : opt_theo.fwd_normalized_put;
|
||||
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;
|
||||
}
|
||||
return Double.NaN;
|
||||
}
|
||||
}
|
||||
public double pnl {
|
||||
@@ -61,6 +67,8 @@ namespace mbts.analytics {
|
||||
rv = _future_pnl(tl_row.price, theo);
|
||||
} else if (tl_row.security_type == SecurityType.Option) {
|
||||
rv = _option_pnl(tl_row.price, theo);
|
||||
} else if (tl_row.security_type == SecurityType.Spot) {
|
||||
rv = tl_row.size * (theo - tl_row.price);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -69,7 +77,10 @@ namespace mbts.analytics {
|
||||
rv = _future_pnl(tl_row.price, theo);
|
||||
} else if (tl_row.security_type == SecurityType.Option) {
|
||||
rv = _option_pnl(tl_row.price, theo);
|
||||
} else if (tl_row.security_type == SecurityType.Spot) {
|
||||
rv = tl_row.size * tl_row.price - settle_price;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ 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;
|
||||
@@ -43,6 +47,8 @@ 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,9 +58,10 @@ namespace BCG {
|
||||
}
|
||||
// TODO: put it as a setting
|
||||
bts.Log.assert(theos.Length == drivers.Length);
|
||||
for (int i = 0; i < theos.Length; i++) {
|
||||
theos[i] /= drivers[i];
|
||||
}
|
||||
// only do this when its deribit crypto option, TODO
|
||||
// for (int i = 0; i < theos.Length; i++) {
|
||||
// theos[i] /= drivers[i];
|
||||
// }
|
||||
return new SpreadValue(spread_spec, theos, deltas, gammas, vegas, adj_vegas, oevs, thetas, slope_vegas, drivers, qs_midpoint_theos, pos, additional_delta);
|
||||
}
|
||||
}
|
||||
@@ -142,9 +143,21 @@ 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, 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();
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
/// this is the only relatively tricky thing here
|
||||
@@ -174,7 +187,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, 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);
|
||||
} 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;
|
||||
@@ -188,7 +201,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, 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, live_symbol_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));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace mbts.ui.controls {
|
||||
namespace mbts.ui.controls {
|
||||
using bts;
|
||||
using bts.utils;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
public const double
|
||||
Account = 62,
|
||||
CheckMark = 42,
|
||||
Currency = 54,
|
||||
Currency = 68,
|
||||
Exchange = 66,
|
||||
FillKey = 68,
|
||||
OrderKey = 78,
|
||||
@@ -43,7 +43,9 @@
|
||||
Side = 47,
|
||||
Qty = 48,
|
||||
MergedQty = 68,
|
||||
NotionalValue = 68,
|
||||
Filled = 56,
|
||||
FilledNotionalValue = 81,
|
||||
Symbol = 60,
|
||||
Time = 112,
|
||||
TimeOfDay = 84,
|
||||
@@ -122,6 +124,7 @@
|
||||
SecurityType = "Security Type",
|
||||
Side = "B/S",
|
||||
Qty = "Qty",
|
||||
NotionalValue = "Notional",
|
||||
MergedQty = "Qty",
|
||||
DirectQty = "Qty (D)",
|
||||
ImpliedQty = "Qty (I)",
|
||||
@@ -144,6 +147,7 @@
|
||||
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
@@ -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 {
|
||||
|
||||
@@ -154,37 +154,48 @@ namespace mbts.ui.spark {
|
||||
|
||||
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);
|
||||
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 StringBuilder sb);
|
||||
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 price_display) {
|
||||
var raw_fmt_size = get_formatter(size_display, display, price_display);
|
||||
return (q, p) => raw_fmt_size(q, 0, p);
|
||||
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 price_display) {
|
||||
var raw_fmt_size = get_formatter(size_display, display, price_display);
|
||||
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.price,
|
||||
nps.notional_size);
|
||||
}
|
||||
|
||||
public static SizeFormatter get_formatter(
|
||||
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable price_display) {
|
||||
_SizeFormatter implied_absent = _get_formatter(size_display.normal, display, price_display);
|
||||
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, price_display);
|
||||
return (merged, implied, price) => {
|
||||
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 sb);
|
||||
(implied > 0 ? implied_present : implied_absent)(ref merged, ref implied, ref price, ref notional, ref sb);
|
||||
return sb.ToString();
|
||||
};
|
||||
}
|
||||
@@ -205,13 +216,13 @@ namespace mbts.ui.spark {
|
||||
static _SizeFormatter _get_formatter(
|
||||
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
|
||||
TickDisplaySettingsImmutable display,
|
||||
TickDisplaySettingsImmutable price_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, price_display));
|
||||
return new _SizeFormatter((ref double m, ref double i, ref double p, ref StringBuilder sb) => {
|
||||
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 sb);
|
||||
f(ref m, ref i, ref p, ref n, ref sb);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -237,7 +248,7 @@ namespace mbts.ui.spark {
|
||||
static _SizeFormatter _get_formatter(
|
||||
MarketSizeDisplay.SizeFormattingElement fmt_element,
|
||||
TickDisplaySettingsImmutable display,
|
||||
TickDisplaySettingsImmutable price_display) =>
|
||||
TickDisplaySettingsImmutable notional_value_display) =>
|
||||
fmt_element.type switch {
|
||||
MarketSizeDisplay.SizeFormattingElementType.Const =>
|
||||
SizeTokenFormatters.char_formatter(fmt_element.data ?? string.Empty),
|
||||
@@ -248,7 +259,7 @@ namespace mbts.ui.spark {
|
||||
MarketSizeDisplay.SizeFormattingElementType.Merged =>
|
||||
SizeTokenFormatters.merged_formatter(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.MergedNotional =>
|
||||
SizeTokenFormatters.merged_notional_formatter(price_display),
|
||||
SizeTokenFormatters.merged_notional_formatter(notional_value_display),
|
||||
_ => throw ExceptionBuilder.Argument.unexpected_case(
|
||||
fmt_element.type, nameof(fmt_element.type)),
|
||||
};
|
||||
@@ -256,24 +267,24 @@ namespace mbts.ui.spark {
|
||||
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 StringBuilder sb) =>
|
||||
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 StringBuilder sb) =>
|
||||
(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 StringBuilder sb) =>
|
||||
(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 StringBuilder sb) =>
|
||||
(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 StringBuilder sb) =>
|
||||
sb.Append(display.format(m * p));
|
||||
(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();
|
||||
@@ -373,9 +384,10 @@ namespace mbts.ui.spark {
|
||||
_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);
|
||||
return impl(merged, implied, 1, merged * SAMPLE_PRICE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ namespace mbts.co2 {
|
||||
|
||||
readonly ulong _numeric_key;
|
||||
|
||||
public void set_bbo(double bid_price, UInt32 bid_qty, double ask_price, UInt32 ask_qty) {
|
||||
|
||||
}
|
||||
|
||||
public FakeMDConsumerHandle(
|
||||
security_key key,
|
||||
ulong numeric_key,
|
||||
@@ -52,6 +56,17 @@ namespace mbts.co2 {
|
||||
public double session_low => 500;
|
||||
public double fixing_price => 20;
|
||||
|
||||
public bool get_bbo(out double bid_price, out float bid_sz, out double ask_price, out float ask_sz) {
|
||||
NativePriceSize bid;
|
||||
NativePriceSize ask;
|
||||
ulong time = 0;
|
||||
bool rv = get_bbo(ref time, out bid, out ask);
|
||||
bid_price = bid.price;
|
||||
bid_sz = bid.size;
|
||||
ask_price = ask.price;
|
||||
ask_sz = ask.size;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public bool get_bbo(ref ulong time, out NativePriceSize bid, out NativePriceSize ask) {
|
||||
bool rv = false;
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace mbts.co2 {
|
||||
bool is_heart_beating { get; }
|
||||
bool has_depth { get; }
|
||||
security_key key { get; }
|
||||
public void set_bbo(double bid_price, UInt32 bid_qty, double ask_price, UInt32 ask_qty);
|
||||
|
||||
// The exchange key can change at CBOE. This numeric_id will always be correct
|
||||
ulong numeric_id { get; }
|
||||
@@ -32,6 +33,7 @@ namespace mbts.co2 {
|
||||
double session_low { get; }
|
||||
double fixing_price { get; }
|
||||
|
||||
bool get_bbo(out double bid_price, out float bid_sz, out double ask_price, out float ask_sz);
|
||||
bool get_bbo(ref ulong time, out NativePriceSize bid, out NativePriceSize ask);
|
||||
|
||||
bool get_depth(
|
||||
|
||||
@@ -4,13 +4,38 @@ using System.Runtime.InteropServices;
|
||||
namespace mbts.co2 {
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct IntPrice : IEquatable<IntPrice> {
|
||||
public static readonly IntPrice NaN = new(0,127);
|
||||
public static readonly IntPrice NaN = new(0, 127);
|
||||
|
||||
[FieldOffset(0)]
|
||||
readonly long _mantissa;
|
||||
[FieldOffset(7)]
|
||||
readonly sbyte _exponent;
|
||||
|
||||
private static bool _is_invalid_double(double value) {
|
||||
return (Double.IsNaN(value) || double.IsInfinity(value) ||
|
||||
value >= long.MaxValue || value <= long.MinValue);
|
||||
}
|
||||
|
||||
private static Tuple<long, sbyte> double_to_mant_exp(double v) {
|
||||
if (_is_invalid_double(v)) {
|
||||
return Tuple.Create<long, sbyte>(0, 127);
|
||||
}
|
||||
sbyte exp = -7;
|
||||
double v_m = v * Math.Pow(10, Math.Abs(exp));
|
||||
if (_is_invalid_double(v_m)) {
|
||||
return Tuple.Create<long, sbyte>(0, 127);
|
||||
}
|
||||
long m = Convert.ToInt64(v_m);
|
||||
return Tuple.Create<long, sbyte>(m, exp);
|
||||
}
|
||||
|
||||
public IntPrice(double v) :
|
||||
this(double_to_mant_exp(v)) {
|
||||
}
|
||||
|
||||
public IntPrice(Tuple<long, sbyte> t)
|
||||
: this(t.Item1, t.Item2) { }
|
||||
|
||||
public IntPrice(long mantissa, sbyte exponent) {
|
||||
_mantissa = mantissa;
|
||||
_exponent = exponent;
|
||||
@@ -38,10 +63,10 @@ namespace mbts.co2 {
|
||||
public static bool operator ==(IntPrice a, IntPrice b) => a.Equals(b);
|
||||
public static bool operator !=(IntPrice a, IntPrice b) => !a.Equals(b);
|
||||
|
||||
public static bool operator < (IntPrice a, IntPrice b) =>
|
||||
public static bool operator <(IntPrice a, IntPrice b) =>
|
||||
a.exponent == b.exponent ? a.mantissa < b.mantissa : a.price() < b.price();
|
||||
|
||||
public static bool operator > (IntPrice a, IntPrice b) =>
|
||||
public static bool operator >(IntPrice a, IntPrice b) =>
|
||||
a.exponent == b.exponent ? a.mantissa > b.mantissa : a.price() > b.price();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@
|
||||
using security_key = System.UInt32;
|
||||
|
||||
public unsafe class MDConsumerHandle : IMDConsumerHandle {
|
||||
public void set_bbo(double bid_price, UInt32 bid_qty, double ask_price, UInt32 ask_qty) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
readonly HotEntry* _hot;
|
||||
readonly HotDepthEntry* _hot_depth;
|
||||
readonly ColdEntry* _cold;
|
||||
@@ -74,6 +77,19 @@
|
||||
public double session_low => _cold->session_low;
|
||||
public double fixing_price => _cold->fixing_price;
|
||||
|
||||
public bool get_bbo(out double bid_price, out float bid_sz, out double ask_price, out float ask_sz) {
|
||||
NativePriceSize bid;
|
||||
NativePriceSize ask;
|
||||
ulong time = 0;
|
||||
bool rv = get_bbo(ref time, out bid, out ask);
|
||||
bid_price = bid.price;
|
||||
bid_sz = bid.size;
|
||||
ask_price = ask.price;
|
||||
ask_sz = ask.size;
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
public bool get_bbo(ref ulong time, out NativePriceSize bid, out NativePriceSize ask) {
|
||||
var updated = market_timestamp;
|
||||
if (updated <= time || updated == TimeStamp.invalid_value) {
|
||||
|
||||
@@ -11,10 +11,11 @@ namespace mbts {
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
public readonly struct SecurityMD : IEquatable<SecurityMD> {
|
||||
public /*readonly*/ struct SecurityMD : IEquatable<SecurityMD> {
|
||||
public static readonly Func<SecurityMD, uint> key_fn = smd => smd.security.Id;
|
||||
|
||||
private IMDConsumerHandle _md;
|
||||
public SecurityMD(
|
||||
SecurityImmutable security,
|
||||
IMDConsumerHandle md,
|
||||
@@ -22,7 +23,7 @@ namespace mbts {
|
||||
TickDisplaySettingsImmutable? quantity_display = null,
|
||||
IEnumerable<TickDisplayRuleImmutable>? price_display_rules = null) {
|
||||
this.security = security;
|
||||
this.md = md;
|
||||
_md = md;
|
||||
this.price_display = price_display ?? Security.Defaults.price_display_settingsi;
|
||||
this.quantity_display = quantity_display ??
|
||||
Security.Defaults.quantity_display_settingsi;
|
||||
@@ -37,7 +38,14 @@ namespace mbts {
|
||||
|
||||
public SecurityImmutable security { get; }
|
||||
public uint id => security.Id;
|
||||
public IMDConsumerHandle md { get; }
|
||||
public IMDConsumerHandle md {
|
||||
get {
|
||||
return _md;
|
||||
}
|
||||
set {
|
||||
_md = value;
|
||||
}
|
||||
}
|
||||
public TickDisplaySettingsImmutable price_display { get; }
|
||||
public TickDisplaySettingsImmutable quantity_display { get; }
|
||||
public ImmutableList<TickDisplayRuleImmutable> price_display_rules { get; }
|
||||
@@ -60,4 +68,128 @@ namespace mbts {
|
||||
public static bool operator !=(SecurityMD left, SecurityMD right) =>
|
||||
!(left == right);
|
||||
}
|
||||
|
||||
static class SecurityMDExtensions {
|
||||
const uint DEPTH_COUNT = 10;
|
||||
[ThreadStatic]
|
||||
static NativePriceSize[] leg1Bids;
|
||||
|
||||
[ThreadStatic]
|
||||
static NativePriceSize[] leg1Asks;
|
||||
|
||||
static SecurityMDExtensions() {
|
||||
leg1Bids = new NativePriceSize[DEPTH_COUNT];
|
||||
leg1Asks = new NativePriceSize[DEPTH_COUNT];
|
||||
}
|
||||
|
||||
public static Book Depth(this SecurityMD security, IReadOnlyDictionary<uint, SecurityMD> securities, ref ulong time) {
|
||||
var bids = new NativePriceSize[DEPTH_COUNT];
|
||||
var asks = new NativePriceSize[DEPTH_COUNT];
|
||||
var bidsOut = new (double, double, double)[(int)DEPTH_COUNT];
|
||||
var asksOut = new (double, double, double)[(int)DEPTH_COUNT];
|
||||
if (!security.security.HasSynthetic) {
|
||||
security.md.get_depth(ref time, ref bids, ref asks, DEPTH_COUNT);
|
||||
for (int i = 0; i < DEPTH_COUNT; i++) {
|
||||
bidsOut[i] = (bids[i].price, bids[i].size, bids[i].notional_size);
|
||||
asksOut[i] = (asks[i].price, asks[i].size, asks[i].notional_size);
|
||||
}
|
||||
return new(bidsOut, asksOut);
|
||||
}
|
||||
Log.assert(security.security.Synthetic.Legs.TryGetFirst(out var leg1));
|
||||
if (!securities.TryGetValue(leg1.Security.Id, out var sec1)) {
|
||||
return Book.Empty;
|
||||
}
|
||||
ulong sec1Time = 0;
|
||||
if (!sec1.md.get_depth(ref sec1Time, ref leg1Bids, ref leg1Asks, DEPTH_COUNT)) {
|
||||
return Book.Empty;
|
||||
}
|
||||
security.md.get_depth(ref time, ref bids, ref asks, DEPTH_COUNT);
|
||||
int leg1BidsLevel = 0;
|
||||
int leg1AsksLevel = 0;
|
||||
double leg1BidsConsumed = 0;
|
||||
double leg1AsksConsumed = 0;
|
||||
double takeQuantity;
|
||||
double notional;
|
||||
for (int i = 0; i < DEPTH_COUNT && !bids[i].is_nan; i++) {
|
||||
takeQuantity = bids[i].size * leg1.Quantity;
|
||||
notional = getNotionalValue(ref leg1Bids, ref leg1BidsLevel, ref leg1BidsConsumed, ref takeQuantity);
|
||||
bidsOut[i] = (bids[i].price, bids[i].size, notional);
|
||||
takeQuantity = asks[i].size * leg1.Quantity;
|
||||
notional = getNotionalValue(ref leg1Asks, ref leg1AsksLevel, ref leg1AsksConsumed, ref takeQuantity);
|
||||
asksOut[i] = (asks[i].price, asks[i].size, notional);
|
||||
}
|
||||
return new(bidsOut, asksOut);
|
||||
}
|
||||
|
||||
public static Book Depth(this SecurityMD security, NativePriceSize[] bids, NativePriceSize[] asks, IReadOnlyDictionary<uint, SecurityMD> securities) {
|
||||
bids ??= new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
|
||||
asks ??= new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
|
||||
var bidsOut = new (double, double, double)[bids.Length];
|
||||
var asksOut = new (double, double, double)[asks.Length];
|
||||
var depthCount = (uint)Math.Min(bids.Length, asks.Length);
|
||||
if (!security.security.HasSynthetic) {
|
||||
for (int i = 0; i < depthCount; i++) {
|
||||
bidsOut[i] = (bids[i].price, bids[i].size, bids[i].notional_size);
|
||||
asksOut[i] = (asks[i].price, asks[i].size, asks[i].notional_size);
|
||||
}
|
||||
return new(bidsOut, asksOut);
|
||||
}
|
||||
Log.assert(security.security.Synthetic.Legs.TryGetFirst(out var leg1));
|
||||
if (!securities.TryGetValue(leg1.Security.Id, out var sec1)) {
|
||||
return Book.Empty;
|
||||
}
|
||||
ulong sec1Time = 0;
|
||||
if (!sec1.md.get_depth(ref sec1Time, ref leg1Bids, ref leg1Asks, depthCount)) {
|
||||
return Book.Empty;
|
||||
}
|
||||
int leg1BidsLevel = 0;
|
||||
int leg1AsksLevel = 0;
|
||||
double leg1BidsConsumed = 0;
|
||||
double leg1AsksConsumed = 0;
|
||||
double takeQuantity;
|
||||
double notional;
|
||||
for (int i = 0; i < depthCount && !bids[i].is_nan; i++) {
|
||||
takeQuantity = bids[i].size;
|
||||
notional = getNotionalValue(ref leg1Bids, ref leg1BidsLevel, ref leg1BidsConsumed, ref takeQuantity);
|
||||
bidsOut[i] = (bids[i].price, bids[i].size, notional);
|
||||
takeQuantity = asks[i].size;
|
||||
notional = getNotionalValue(ref leg1Asks, ref leg1AsksLevel, ref leg1AsksConsumed, ref takeQuantity);
|
||||
asksOut[i] = (asks[i].price, asks[i].size, notional);
|
||||
}
|
||||
return new(bidsOut, asksOut);
|
||||
}
|
||||
|
||||
static double getNotionalValue(ref NativePriceSize[] depth, ref int level, ref double levelConsumed, ref double takeQuantity) {
|
||||
if (level >= depth.Length) {
|
||||
return double.NaN;
|
||||
}
|
||||
double notionalValue = 0;
|
||||
while (takeQuantity > MathUtils.epsilon<double>() && level < depth.Length && !depth[level].is_nan) {
|
||||
var lvlDepth = (double)depth[level].size - levelConsumed;
|
||||
if (lvlDepth <= 0) {
|
||||
level++;
|
||||
levelConsumed = 0;
|
||||
continue;
|
||||
}
|
||||
var lvlTake = Math.Min(takeQuantity, lvlDepth);
|
||||
notionalValue += lvlTake * depth[level].price;
|
||||
levelConsumed += lvlTake;
|
||||
takeQuantity -= lvlDepth;
|
||||
}
|
||||
return notionalValue;
|
||||
}
|
||||
|
||||
public class Book {
|
||||
public Book(IEnumerable<(double Price, double Volume, double NotionalValue)> bids, IEnumerable<(double Price, double Quantity, double NotionalValue)> asks) {
|
||||
Bids = bids;
|
||||
Asks = asks;
|
||||
}
|
||||
|
||||
public static Book Empty =>
|
||||
new(Array.Empty<(double, double, double)>(), Array.Empty<(double, double, double)>());
|
||||
|
||||
public IEnumerable<(double Price, double Volume, double NotionalValue)> Bids { get; }
|
||||
public IEnumerable<(double Price, double Volume, double NotionalValue)> Asks { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,20 @@ 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,6 +48,12 @@ 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;
|
||||
|
||||
@@ -487,5 +493,8 @@ namespace Spark.Api.Orders {
|
||||
CustomOrderDefinition.CustomOneofCase.Composite => "COMP",
|
||||
_ => algo_type.ToString(),
|
||||
};
|
||||
|
||||
public static double notional_value(this FillImmutable fill) =>
|
||||
fill.LastShares * fill.LastPrice;
|
||||
}
|
||||
}
|
||||
@@ -10,58 +10,64 @@ using System.IO;
|
||||
using System.Reactive.Disposables;
|
||||
using mbts;
|
||||
using System.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
||||
namespace bts.api.core {
|
||||
|
||||
internal class QuickbusClientChannel : ClientChannel {
|
||||
IntPtr _client;
|
||||
internal class QuickbusClientChannel : ClientChannel {
|
||||
IntPtr _client;
|
||||
|
||||
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
|
||||
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
|
||||
}
|
||||
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
|
||||
Log.QBus.diag($"Created QuickbusClientChannel");
|
||||
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
ApiNativeMethods.api_client_destroy(_client);
|
||||
_client = IntPtr.Zero;
|
||||
}
|
||||
public override void Dispose() {
|
||||
Log.QBus.diag($"Disposed QuickbusClientChannel");
|
||||
ApiNativeMethods.api_client_destroy(_client);
|
||||
_client = IntPtr.Zero;
|
||||
}
|
||||
|
||||
|
||||
int _req_id = 0;
|
||||
Dictionary<int, PBCallbackWrapper> _keepalive_cache = new();
|
||||
|
||||
unsafe public override Task<Result<Response>> send<Request, Response>(int method_id, Request request) {
|
||||
var id = Interlocked.Increment(ref _req_id);
|
||||
var header = new RequestHeader() {
|
||||
Method = method_id,
|
||||
RequestId = id,
|
||||
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
|
||||
};
|
||||
var buffer = header.serialize(request);
|
||||
var tcs = new TaskCompletionSource<Result<Response>>();
|
||||
//ApiNativeMethods.api_client_log_error($"Call {id}");
|
||||
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
|
||||
//ApiNativeMethods.api_client_log_error($"Reply {id}");
|
||||
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
|
||||
if (res.header.Code == ApiError.Success) {
|
||||
tcs.TrySetResult(new Result<Response>(res.body));
|
||||
}
|
||||
else {
|
||||
tcs.TrySetResult(new Result<Response>(res.header.Code));
|
||||
}
|
||||
var id = Interlocked.Increment(ref _req_id);
|
||||
var header = new RequestHeader() {
|
||||
Method = method_id,
|
||||
RequestId = id,
|
||||
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
|
||||
};
|
||||
var buffer = header.serialize(request);
|
||||
var tcs = new TaskCompletionSource<Result<Response>>();
|
||||
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
|
||||
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
|
||||
if (res.header.Code == ApiError.Success) {
|
||||
tcs.TrySetResult(new Result<Response>(res.body));
|
||||
}
|
||||
else {
|
||||
tcs.TrySetResult(new Result<Response>(res.header.Code));
|
||||
}
|
||||
lock (_keepalive_cache) {
|
||||
_keepalive_cache.Remove(id);
|
||||
}
|
||||
};
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
|
||||
var cb_wrapper = new PBCallbackWrapper(callback);
|
||||
lock (_keepalive_cache) {
|
||||
_keepalive_cache.Remove(id);
|
||||
_keepalive_cache.Add(id, cb_wrapper);
|
||||
}
|
||||
};
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
|
||||
var cb_wrapper = new PBCallbackWrapper(callback);
|
||||
lock (_keepalive_cache) {
|
||||
_keepalive_cache.Add(id, cb_wrapper);
|
||||
}
|
||||
ApiNativeMethods.api_client_send(
|
||||
_client, buffer, buffer.Length, cb_wrapper.callback, IntPtr.Zero);
|
||||
return tcs.Task;
|
||||
}
|
||||
ApiNativeMethods.api_client_send(
|
||||
_client, buffer, buffer.Length, cb_wrapper.Callback, IntPtr.Zero);
|
||||
// Return a task that will free the handle once the task is completed
|
||||
return tcs.Task.ContinueWith(task => {
|
||||
cb_wrapper.Free(); // Ensure the GCHandle is freed when the callback is done
|
||||
return task.Result;
|
||||
});
|
||||
}
|
||||
|
||||
public override IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(int method_id, string method, string qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter) {
|
||||
var result = Observable.Create<RawUpdate<TOut>>(async observer => {
|
||||
@@ -206,11 +212,29 @@ namespace bts.api.core {
|
||||
/// garbage-collector race may yield the infamous error, "A callback was made on a garbage
|
||||
/// collected delegate of type...").
|
||||
/// </remarks>
|
||||
class PBCallbackWrapper {
|
||||
public readonly ApiNativeMethods.raw_pb_callback callback;
|
||||
class PBCallbackWrapper
|
||||
{
|
||||
public readonly ApiNativeMethods.raw_pb_callback Callback;
|
||||
private GCHandle _handle;
|
||||
|
||||
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback) =>
|
||||
this.callback = callback;
|
||||
}
|
||||
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback)
|
||||
{
|
||||
// Pin the callback to prevent garbage collection
|
||||
Log.QBus.diag($"Created PBCallbackWrapper");
|
||||
_handle = GCHandle.Alloc(callback);
|
||||
Callback = callback;
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
// Free the GCHandle when the delegate is no longer needed
|
||||
Log.QBus.diag($"Deleting PBCallbackWrapper");
|
||||
|
||||
if (_handle.IsAllocated)
|
||||
{
|
||||
_handle.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
@@ -10,8 +10,9 @@ 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 BTS with {(config_source is null ? "defaults" : $"'{config_source}'")}");
|
||||
$"Configuring Spark {version} with {(config_source is null ? "defaults" : $"'{config_source}'")}");
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
utils.BTSNativeMethods.provide_command_line(args, args.Length);
|
||||
|
||||
|
||||
@@ -65,10 +65,10 @@ namespace bts.utils {
|
||||
public static bool epsilon_equals<T>(this T x, T y, T epsilon) where T : IFloatingPoint<T> =>
|
||||
MathUtils.epsilon_equals(x, y, epsilon);
|
||||
|
||||
public static bool marginal_equals<T>(T x, T y) where T : IFloatingPoint<T> =>
|
||||
marginal_equals(x, y, MathUtils.epsilon<T>());
|
||||
public static bool MarginalEquals<T>(this T x, T y) where T : IFloatingPoint<T> =>
|
||||
MarginalEquals(x, y, MathUtils.epsilon<T>());
|
||||
|
||||
public static bool marginal_equals<T>(T x, T y, T margin) where T : IFloatingPoint<T> =>
|
||||
public static bool MarginalEquals<T>(this T x, T y, T margin) where T : IFloatingPoint<T> =>
|
||||
MathUtils.marginal_equals(x, y, margin);
|
||||
|
||||
public static bool is_within_range<T>(
|
||||
|
||||
@@ -13,6 +13,7 @@ message DatabentoMarketDataConfig {
|
||||
double replay_sleep_timer_sec = 50;
|
||||
string key = 60;
|
||||
string dataset = 70;
|
||||
bool filter_securities_one_year= 80;
|
||||
uint32 filter_securities_upto_n_months= 80;
|
||||
bool load_reference_data = 90;
|
||||
uint32 debug_sec = 100;
|
||||
}
|
||||
@@ -4,16 +4,11 @@
|
||||
#include "lib/api/services/OrderService.server.pb.h"
|
||||
#include "lib/config/config.h"
|
||||
#include "lib/config/protos/toggles.pb.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include <memory>
|
||||
|
||||
namespace bts {
|
||||
namespace admin {
|
||||
|
||||
using namespace bts::api::admin;
|
||||
using namespace bts::api::orderman;
|
||||
|
||||
class AdminServiceUtils {
|
||||
public:
|
||||
struct ExchangeSubAccountResolver {
|
||||
@@ -33,7 +28,7 @@ class AdminServiceUtils {
|
||||
return is_int_account ? entry.bts_account_name()
|
||||
: entry.exchange_subaccount();
|
||||
};
|
||||
if(sec == nullptr)
|
||||
if (sec == nullptr)
|
||||
return nullptr;
|
||||
for (const auto &u : exch_sub_accounts.accounts()) {
|
||||
if (get_account(u) == account)
|
||||
@@ -59,224 +54,247 @@ class AdminServiceUtils {
|
||||
////////////////////////////////////////////
|
||||
// finds the max id out of all the existing connections
|
||||
// (todo: deleted connections)
|
||||
static uint32_t find_max_connection_id(const AdminData &data);
|
||||
static uint32_t find_max_connection_id(const api::admin::AdminData &data);
|
||||
// finds a connection by name and returns a mutable pointer to it
|
||||
static const ExchangeConnection *find_connection(
|
||||
const AdminData &data, const std::string &connection_name);
|
||||
static const api::admin::ExchangeConnection *find_connection(
|
||||
const api::admin::AdminData &data, const std::string &connection_name);
|
||||
// finds the presence of an exchange in a connection
|
||||
static bool has_exchange(
|
||||
const ExchangeConnection &conn, bts::api::Venue exchange);
|
||||
const api::admin::ExchangeConnection &conn, api::Venue exchange);
|
||||
// finds the default connections for an exchange
|
||||
// in a given account
|
||||
static const ExchangeConnection *find_default_account_connection(
|
||||
const AdminData &data,
|
||||
const BTSAccount &account,
|
||||
bts::api::Venue exchange);
|
||||
static const ExchangeConnection *resolve_cme_connection(
|
||||
const AdminData &data,
|
||||
const BTSAccount &account,
|
||||
static const api::admin::ExchangeConnection *find_default_account_connection(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
api::Venue exchange);
|
||||
static const api::admin::ExchangeConnection *resolve_cme_connection(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
const api::Security &sec);
|
||||
static const ExchangeConnection *resolve_ice_connection(
|
||||
const AdminData &data,
|
||||
const BTSAccount &account,
|
||||
static const api::admin::ExchangeConnection *resolve_ice_connection(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
const api::Security &sec);
|
||||
static const ExchangeConnection *resolve_sim_connection(
|
||||
const AdminData &data,
|
||||
const BTSAccount &account,
|
||||
static const api::admin::ExchangeConnection *resolve_sim_connection(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
const api::Security &sec);
|
||||
static const ExchangeConnection *resolve_connection(
|
||||
const AdminData &data,
|
||||
const BTSAccount &account,
|
||||
const bts::api::Security &sec);
|
||||
static const api::admin::ExchangeConnection *resolve_connection(
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
const api::Security &sec);
|
||||
|
||||
////////////////////////////////////////////
|
||||
// connection mutators /////////////////////
|
||||
////////////////////////////////////////////
|
||||
// adds a new connection (does not check for name uniqueness)
|
||||
static ExchangeConnection *add_connection(
|
||||
AdminData &data, const ExchangeConnection &new_connection);
|
||||
static api::admin::ExchangeConnection *add_connection(
|
||||
api::admin::AdminData &data,
|
||||
const api::admin::ExchangeConnection &new_connection);
|
||||
// reads the connections from config and fills in the
|
||||
// admin data (with names only)
|
||||
static void fill_connections_from_config(AdminData &admin_data);
|
||||
static void fill_connections_from_config(api::admin::AdminData &admin_data);
|
||||
|
||||
////////////////////////////////////////////
|
||||
// account accessors ///////////////////////
|
||||
////////////////////////////////////////////
|
||||
// finds the account from the name and returns
|
||||
// a mutable pointer to the account
|
||||
static BTSAccount *find_account(
|
||||
AdminData &data,
|
||||
static api::admin::BTSAccount *find_account(
|
||||
api::admin::AdminData &data,
|
||||
const std::string &acct_name,
|
||||
bool include_deleted = false);
|
||||
static std::map<std::string, uint32_t> get_clearing_account_map(
|
||||
const AdminData &);
|
||||
const api::admin::AdminData &);
|
||||
|
||||
// finds the account from a clearing account (and its venue)
|
||||
static BTSAccount const *find_account(
|
||||
const AdminData &data,
|
||||
static api::admin::BTSAccount const *find_account(
|
||||
const api::admin::AdminData &data,
|
||||
const std::string &clearing_account,
|
||||
bts::api::Venue);
|
||||
api::Venue);
|
||||
// same as above for const pointer
|
||||
static const BTSAccount *find_account(
|
||||
const AdminData &data,
|
||||
static const api::admin::BTSAccount *find_account(
|
||||
const api::admin::AdminData &data,
|
||||
const std::string &acct_name,
|
||||
bool include_deleted = false);
|
||||
// finds the account from the id and returns
|
||||
// a mutable pointer to the account
|
||||
static BTSAccount *find_account(
|
||||
AdminData &data, uint32_t id, bool include_deleted = false);
|
||||
static api::admin::BTSAccount *find_account(
|
||||
api::admin::AdminData &data, uint32_t id, bool include_deleted = false);
|
||||
// like above but returns a const pointer
|
||||
static const BTSAccount *find_account(
|
||||
const AdminData &data, uint32_t id, bool include_deleted = false);
|
||||
static const api::admin::BTSAccount *find_account(
|
||||
const api::admin::AdminData &data,
|
||||
uint32_t id,
|
||||
bool include_deleted = false);
|
||||
// this function is an indirection layer for account access
|
||||
// so deletions are dealt in a more deliberate manner
|
||||
static std::vector<const BTSAccount *> accounts(
|
||||
const AdminData &data, bool include_deleted);
|
||||
static std::vector<BTSAccount *> accounts_mutable(
|
||||
AdminData &data, bool include_deleted);
|
||||
static std::vector<const api::admin::BTSAccount *> accounts(
|
||||
const api::admin::AdminData &data, bool include_deleted);
|
||||
static std::vector<api::admin::BTSAccount *> accounts_mutable(
|
||||
api::admin::AdminData &data, bool include_deleted);
|
||||
// finds the max id amongts all the existing accounts
|
||||
static uint32_t find_max_acct_id(const AdminData &data);
|
||||
static uint32_t find_max_acct_id(const api::admin::AdminData &data);
|
||||
// gets the account data for cme (and goes up the hierarchy
|
||||
// if needed)
|
||||
static const CMEAccountData *get_account_cme_data(
|
||||
const BTSAccount &account, const AdminData &data);
|
||||
static const ICEAccountData *get_account_ice_data(
|
||||
const BTSAccount &account, const AdminData &data);
|
||||
static const SIMAccountData *get_account_sim_data(
|
||||
const BTSAccount &account, const AdminData &data);
|
||||
static const api::admin::CMEAccountData *get_account_cme_data(
|
||||
const api::admin::BTSAccount &account,
|
||||
const api::admin::AdminData &data);
|
||||
static const api::admin::ICEAccountData *get_account_ice_data(
|
||||
const api::admin::BTSAccount &account,
|
||||
const api::admin::AdminData &data);
|
||||
static const api::admin::SIMAccountData *get_account_sim_data(
|
||||
const api::admin::BTSAccount &account,
|
||||
const api::admin::AdminData &data);
|
||||
// returns true if an account has a connection
|
||||
bool static account_has_connection(
|
||||
const BTSAccount &account, const std::string &connection_name);
|
||||
const api::admin::BTSAccount &account,
|
||||
const std::string &connection_name);
|
||||
// finds the external/clearing account associated
|
||||
// with an account
|
||||
static std::string find_account_exchange_account(
|
||||
const AdminData &data,
|
||||
const BTSAccount &account,
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
api::Venue exchange,
|
||||
AdminServiceOperationResult &out_result);
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
// returns a map of the accounts vs their 'is_active'
|
||||
static std::map<uint32_t, bool> accounts_map(const AdminData &admin_data);
|
||||
static std::map<uint32_t, bool> accounts_map(
|
||||
const api::admin::AdminData &admin_data);
|
||||
// validates that the update to the account is valid
|
||||
static void validate_account_update(
|
||||
const AdminData &admin_data,
|
||||
const BTSAccount &updated_account,
|
||||
AdminServiceOperationResult &result);
|
||||
const api::admin::AdminData &admin_data,
|
||||
const api::admin::BTSAccount &updated_account,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
// validates that the account exchange changes are valid
|
||||
// (ex > 12 characters for accounts at CME)
|
||||
static void validate_account_exchange_data(
|
||||
const BTSAccount &account, AdminServiceOperationResult &out_result);
|
||||
static RoutingRecord get_routing_record(
|
||||
const AdminData &data,
|
||||
const api::admin::BTSAccount &account,
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
static api::orderman::RoutingRecord get_routing_record(
|
||||
const api::admin::AdminData &data,
|
||||
const std::string &account_name,
|
||||
const std::string &user_name,
|
||||
const bts::api::Security &sec,
|
||||
AdminServiceOperationResult &out_result);
|
||||
static RoutingRecord get_routing_record(
|
||||
const AdminData &data,
|
||||
const api::Security &sec,
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
static api::orderman::RoutingRecord get_routing_record(
|
||||
const api::admin::AdminData &data,
|
||||
uint32_t account_id,
|
||||
uint32_t user_id,
|
||||
const bts::api::Security &sec,
|
||||
AdminServiceOperationResult &out_result);
|
||||
const api::Security &sec,
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
|
||||
////////////////////////////////////////////
|
||||
// account mutators ////////////////////////
|
||||
////////////////////////////////////////////
|
||||
static BTSAccount *create_account(
|
||||
AdminData &data,
|
||||
static api::admin::BTSAccount *create_account(
|
||||
api::admin::AdminData &data,
|
||||
uint32_t parent_account_id,
|
||||
const std::string &account_name,
|
||||
const std::string &description,
|
||||
bool is_active,
|
||||
AdminServiceCreateAccountResult &result);
|
||||
api::admin::AdminServiceCreateAccountResult &result);
|
||||
// writes over the account data completely
|
||||
static void update_account(
|
||||
AdminData &data,
|
||||
const BTSAccount &updated_account,
|
||||
AdminServiceOperationResult &result);
|
||||
api::admin::AdminData &data,
|
||||
const api::admin::BTSAccount &updated_account,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
static void delete_account(
|
||||
AdminData &admin_data,
|
||||
const IDAPIArgument &id,
|
||||
AdminServiceOperationResult &result);
|
||||
api::admin::AdminData &admin_data,
|
||||
const api::admin::IDAPIArgument &id,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
static void set_account_exchange_data(
|
||||
BTSAccount *account, const ExchangeAccountData &exchange_data);
|
||||
api::admin::BTSAccount *account,
|
||||
const api::admin::ExchangeAccountData &exchange_data);
|
||||
// this is not a generally useful function but since
|
||||
// connection are in config, this cleans up the account's
|
||||
// connections and syncs it with the list in AdminData
|
||||
// connections and syncs it with the list in api::admin::AdminData
|
||||
static void sync_account_connections(
|
||||
const AdminData &data, BTSAccount &account);
|
||||
const api::admin::AdminData &data, api::admin::BTSAccount &account);
|
||||
|
||||
////////////////////////////////////////////
|
||||
// group accessors /////////////////////////
|
||||
////////////////////////////////////////////
|
||||
// given an id, finds the mutable pointer to a group
|
||||
static BTSUserGroup *find_group(AdminData &data, uint32_t id);
|
||||
static api::admin::BTSUserGroup *find_group(
|
||||
api::admin::AdminData &data, uint32_t id);
|
||||
// given an id, finds the const pointer to a group
|
||||
static const BTSUserGroup *const find_group(
|
||||
const AdminData &data, uint32_t id);
|
||||
static const api::admin::BTSUserGroup *const find_group(
|
||||
const api::admin::AdminData &data, uint32_t id);
|
||||
// given a name, finds the mutable pointer to a group
|
||||
static BTSUserGroup *find_group(
|
||||
AdminData &data, const std::string &group_name);
|
||||
static api::admin::BTSUserGroup *find_group(
|
||||
api::admin::AdminData &data, const std::string &group_name);
|
||||
// finds the max id out of all the groups
|
||||
static uint32_t find_max_group_id(const AdminData &data);
|
||||
static uint32_t find_max_group_id(const api::admin::AdminData &data);
|
||||
// travels up the group tree until it finds a populated cme data
|
||||
static const CMEUserData *get_group_cme_data(
|
||||
const BTSUserGroup &group, const AdminData &data);
|
||||
static const ICEUserData *get_group_ice_data(
|
||||
const BTSUserGroup &group, const AdminData &data);
|
||||
static const SIMUserData *get_group_sim_data(
|
||||
const BTSUserGroup &group, const AdminData &data);
|
||||
static const UserAccountPermissions *const get_group_user_permissions(
|
||||
const bts::api::admin::AdminData &admin_data, uint32_t group_id);
|
||||
static const api::admin::CMEUserData *get_group_cme_data(
|
||||
const api::admin::BTSUserGroup &group,
|
||||
const api::admin::AdminData &data);
|
||||
static const api::admin::ICEUserData *get_group_ice_data(
|
||||
const api::admin::BTSUserGroup &group,
|
||||
const api::admin::AdminData &data);
|
||||
static const api::admin::SIMUserData *get_group_sim_data(
|
||||
const api::admin::BTSUserGroup &group,
|
||||
const api::admin::AdminData &data);
|
||||
static const api::admin::UserAccountPermissions *const
|
||||
get_group_user_permissions(
|
||||
const api::admin::AdminData &admin_data, uint32_t group_id);
|
||||
|
||||
////////////////////////////////////////////
|
||||
// group mutators/ /////////////////////////
|
||||
////////////////////////////////////////////
|
||||
static BTSUserGroup *create_group(
|
||||
AdminData &data,
|
||||
static api::admin::BTSUserGroup *create_group(
|
||||
api::admin::AdminData &data,
|
||||
uint32_t parent_group_id,
|
||||
const std::string &group_name,
|
||||
AdminServiceOperationResult &result);
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
|
||||
////////////////////////////////////////////
|
||||
// user accessors //////////////////////////
|
||||
////////////////////////////////////////////
|
||||
static BTSUser *find_user(
|
||||
AdminData &data,
|
||||
static api::admin::BTSUser *find_user(
|
||||
api::admin::AdminData &data,
|
||||
const std::string &user_name,
|
||||
bool include_deleted = false);
|
||||
static const BTSUser *find_user(
|
||||
const AdminData &data,
|
||||
static const api::admin::BTSUser *find_user(
|
||||
const api::admin::AdminData &data,
|
||||
const std::string &user_name,
|
||||
bool include_deleted = false);
|
||||
static BTSUser *find_user(
|
||||
AdminData &data, uint32_t id, bool include_deleted = false);
|
||||
static const BTSUser *const find_user(
|
||||
const AdminData &data, uint32_t id, bool include_deleted = false);
|
||||
static std::vector<const BTSUser *> users(
|
||||
const AdminData &data, bool include_deleted);
|
||||
static std::vector<BTSUser *> users_mutable(
|
||||
AdminData &data, bool include_deleted);
|
||||
static uint32_t find_max_user_id(const AdminData &data);
|
||||
static const CMEUserData *get_user_cme_data(
|
||||
const BTSUser &user, const AdminData &data);
|
||||
static const ICEUserData *get_user_ice_data(
|
||||
const BTSUser &user, const AdminData &data);
|
||||
static const SIMUserData *get_user_sim_data(
|
||||
const BTSUser &user, const AdminData &data);
|
||||
static api::admin::BTSUser *find_user(
|
||||
api::admin::AdminData &data, uint32_t id, bool include_deleted = false);
|
||||
static const api::admin::BTSUser *const find_user(
|
||||
const api::admin::AdminData &data,
|
||||
uint32_t id,
|
||||
bool include_deleted = false);
|
||||
static std::vector<const api::admin::BTSUser *> users(
|
||||
const api::admin::AdminData &data, bool include_deleted);
|
||||
static std::vector<api::admin::BTSUser *> users_mutable(
|
||||
api::admin::AdminData &data, bool include_deleted);
|
||||
static uint32_t find_max_user_id(const api::admin::AdminData &data);
|
||||
static const api::admin::CMEUserData *get_user_cme_data(
|
||||
const api::admin::BTSUser &user, const api::admin::AdminData &data);
|
||||
static const api::admin::ICEUserData *get_user_ice_data(
|
||||
const api::admin::BTSUser &user, const api::admin::AdminData &data);
|
||||
static const api::admin::SIMUserData *get_user_sim_data(
|
||||
const api::admin::BTSUser &user, const api::admin::AdminData &data);
|
||||
// finds the "exchange user" (ex: "operator_id" at CME or "authorized_trader"
|
||||
// at ICE) for a user
|
||||
static std::string find_user_exchange_id(
|
||||
const AdminData &data,
|
||||
const BTSUser &user,
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSUser &user,
|
||||
api::Venue exchange,
|
||||
AdminServiceOperationResult &out_result);
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
// i. uses the exchange and exchange_user to reverse lookup the user(s)
|
||||
// ii. uses the exchange, connection and exchange_account to reverse lookup
|
||||
// the account(s) iii. finds the first account from ii that a user from i is
|
||||
// authorized to trade
|
||||
static std::string find_user_company_id(
|
||||
const AdminData &data,
|
||||
const BTSUser &user,
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSUser &user,
|
||||
api::Venue exchange,
|
||||
AdminServiceOperationResult &out_result);
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
|
||||
static std::pair<const BTSAccount *, const BTSUser *>
|
||||
static std::pair<const api::admin::BTSAccount *, const api::admin::BTSUser *>
|
||||
find_user_account_matches(
|
||||
const AdminData &admin_data,
|
||||
const api::admin::AdminData &admin_data,
|
||||
const std::string &connection,
|
||||
const std::string &exchange_account,
|
||||
const std::string &exchange_user,
|
||||
@@ -284,100 +302,108 @@ class AdminServiceUtils {
|
||||
// for a given user finds the account permissions (if permissions are
|
||||
// derived from the group, it traverses the group hierarcy to find the
|
||||
// correct permissions.
|
||||
static const UserAccountPermissions *get_user_permissions(
|
||||
const AdminData &admin_data, uint32_t user_id);
|
||||
static const api::admin::UserAccountPermissions *get_user_permissions(
|
||||
const api::admin::AdminData &admin_data, uint32_t user_id);
|
||||
// for a given user, fills in the associated_accounts ref arg
|
||||
// with the accounts the user has permission to trade in
|
||||
static void find_user_trading_accounts(
|
||||
const AdminData &admin_data,
|
||||
const api::admin::AdminData &admin_data,
|
||||
uint32_t user_id,
|
||||
std::vector<const BTSAccount *> &associated_accounts);
|
||||
std::vector<const api::admin::BTSAccount *> &associated_accounts);
|
||||
// for a given account, fills in the associated_users ref arg
|
||||
// with the users that can trade into the account
|
||||
static void find_users_for_trading_account(
|
||||
const AdminData &admin_data,
|
||||
const api::admin::AdminData &admin_data,
|
||||
uint32_t account_id,
|
||||
std::vector<const BTSUser *> &associated_users);
|
||||
std::vector<const api::admin::BTSUser *> &associated_users);
|
||||
// validates that changes in user data satisfy all the
|
||||
// constraints
|
||||
static void validate_user_update(
|
||||
const AdminData &admin_data,
|
||||
const BTSUser &updated_user,
|
||||
AdminServiceOperationResult &result);
|
||||
const api::admin::AdminData &admin_data,
|
||||
const api::admin::BTSUser &updated_user,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
// vaidates the exchange part of user data updates
|
||||
static void validate_user_exchange_data(
|
||||
const BTSUser &updated_user, AdminServiceOperationResult &out_result);
|
||||
const api::admin::BTSUser &updated_user,
|
||||
api::admin::AdminServiceOperationResult &out_result);
|
||||
static void get_md_entitlement_report(
|
||||
const AdminData &admin_data,
|
||||
std::vector<MDEntitlementReportUnit> &entitlements);
|
||||
const api::admin::AdminData &admin_data,
|
||||
std::vector<api::admin::MDEntitlementReportUnit> &entitlements);
|
||||
// Gets a reporting unit out of CMEMicData
|
||||
static MDEntitlementReportUnit get_cme_md_entitlement_unit(
|
||||
const BTSUser &user, const CMEMicData &cme_mic_data);
|
||||
static MDEntitlementReportUnit get_ice_md_entitlement_unit(
|
||||
const BTSUser &user, const ICEMicData &ice_mic_data);
|
||||
static api::admin::MDEntitlementReportUnit get_cme_md_entitlement_unit(
|
||||
const api::admin::BTSUser &user,
|
||||
const api::admin::CMEMicData &cme_mic_data);
|
||||
static api::admin::MDEntitlementReportUnit get_ice_md_entitlement_unit(
|
||||
const api::admin::BTSUser &user,
|
||||
const api::admin::ICEMicData &ice_mic_data);
|
||||
static void get_user_symbol_permissions(
|
||||
const AdminData &data,
|
||||
const BTSUser &user,
|
||||
std::vector<UserSymbolMDPermissionUnit> &res);
|
||||
const api::admin::AdminData &data,
|
||||
const api::admin::BTSUser &user,
|
||||
std::vector<api::admin::UserSymbolMDPermissionUnit> &res);
|
||||
|
||||
////////////////////////////////////////////
|
||||
// user mutators ///////////////////////////
|
||||
////////////////////////////////////////////
|
||||
static BTSUser *create_user(
|
||||
AdminData &data,
|
||||
static api::admin::BTSUser *create_user(
|
||||
api::admin::AdminData &data,
|
||||
uint32_t parent_group_id,
|
||||
const std::string &name,
|
||||
const std::string &password,
|
||||
const std::string &full_name,
|
||||
const std::string &company,
|
||||
bool is_active,
|
||||
const LicenseData &license_data,
|
||||
const LicenseEntitlements &license_entitlements,
|
||||
AdminServiceCreateEntityResult &result);
|
||||
static BTSUser *update_user(
|
||||
AdminData &admin_data,
|
||||
const BTSUser &updated_user,
|
||||
AdminServiceOperationResult &result);
|
||||
const api::admin::LicenseData &license_data,
|
||||
const api::admin::LicenseEntitlements &license_entitlements,
|
||||
api::admin::AdminServiceCreateEntityResult &result);
|
||||
static api::admin::BTSUser *update_user(
|
||||
api::admin::AdminData &admin_data,
|
||||
const api::admin::BTSUser &updated_user,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
static void delete_user(
|
||||
AdminData &admin_data,
|
||||
const IDAPIArgument &id,
|
||||
AdminServiceOperationResult &result);
|
||||
api::admin::AdminData &admin_data,
|
||||
const api::admin::IDAPIArgument &id,
|
||||
api::admin::AdminServiceOperationResult &result);
|
||||
static void set_user_exchange_data(
|
||||
BTSUser *user, const ExchangeUserData &exchange_user_data);
|
||||
static void clear_user_accounts(BTSUser *user);
|
||||
api::admin::BTSUser *user,
|
||||
const api::admin::ExchangeUserData &exchange_user_data);
|
||||
static void clear_user_accounts(api::admin::BTSUser *user);
|
||||
static void set_user_accounts(
|
||||
BTSUser *user, const UserAccountPermissions &user_account_permissions);
|
||||
api::admin::BTSUser *user,
|
||||
const api::admin::UserAccountPermissions &user_account_permissions);
|
||||
|
||||
////////////////////////////////////////////
|
||||
// risk limits //////////////////////////////
|
||||
////////////////////////////////////////////
|
||||
// sorting function used by sort_limits to
|
||||
// sort SecurityScope's
|
||||
static bool _sort_security_scopes(
|
||||
const SecurityScope &a, const SecurityScope &b);
|
||||
const api::admin::SecurityScope &a, const api::admin::SecurityScope &b);
|
||||
// sorting function used by sort_limits to
|
||||
// sort ProductScope's
|
||||
static bool _sort_product_scopes(
|
||||
const ProductScope &a, const ProductScope &);
|
||||
const api::admin::ProductScope &a, const api::admin::ProductScope &);
|
||||
// this sorts the rules such that the most
|
||||
// specific ones are at the top. this makes it
|
||||
// easier to match rules with securities/products
|
||||
static void sort_limits(BTSAccount &account);
|
||||
// creates a copy of AdminData with sorted rules
|
||||
static AdminData sort_limits(const AdminData &admin_data);
|
||||
static void sort_limits(api::admin::BTSAccount &account);
|
||||
// creates a copy of api::admin::AdminData with sorted rules
|
||||
static api::admin::AdminData sort_limits(
|
||||
const api::admin::AdminData &admin_data);
|
||||
// find the clip size for a security, account
|
||||
static const uint32_t find_account_security_clip_size(
|
||||
const BTSAccount &account, const api::Security &security);
|
||||
const api::admin::BTSAccount &account, const api::Security &security);
|
||||
// find the position limit for an account, security
|
||||
static const AccountSecurityPositionLimitUnit *
|
||||
static const api::admin::AccountSecurityPositionLimitUnit *
|
||||
find_account_security_position_limit(
|
||||
const BTSAccount &account, const api::Security &security);
|
||||
const api::admin::BTSAccount &account, const api::Security &security);
|
||||
// find the position limit for an account, product
|
||||
static const AccountProductPositionLimitUnit *
|
||||
static const api::admin::AccountProductPositionLimitUnit *
|
||||
find_account_product_position_limit(
|
||||
const BTSAccount &account, const api::Product &product);
|
||||
const api::admin::BTSAccount &account, const api::Product &product);
|
||||
static bool match_security_scope(
|
||||
const SecurityScope &scope, const api::Security &security);
|
||||
const api::admin::SecurityScope &scope, const api::Security &security);
|
||||
static bool match_product_scope(
|
||||
const ProductScope &scope, const api::Product &product);
|
||||
const api::admin::ProductScope &scope, const api::Product &product);
|
||||
static bool match_product(
|
||||
const api::Security &security, const api::Product &product);
|
||||
static bool _match_limit_rule_symbol(
|
||||
@@ -388,13 +414,14 @@ class AdminServiceUtils {
|
||||
////////////////////////////////////////////
|
||||
static bool has_id(uint32_t id);
|
||||
static bool is_symbol_set(const std::string &symbol);
|
||||
static void bootstrap_data(bts::api::admin::AdminData &data);
|
||||
static void bootstrap_data(api::admin::AdminData &data);
|
||||
static void get_spread_prod_qty(
|
||||
const api::Security &s,
|
||||
std::map<std::string, double> &out_map,
|
||||
double mult = 1.0);
|
||||
// returns true if this spread effects the position of any outrights
|
||||
static bool residual_pos_spread(const api::Security &s);
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace admin
|
||||
} // namespace bts
|
||||
@@ -65,6 +65,7 @@ function(get_cpp_sources dest)
|
||||
domain/DomainSecurityUtils.cpp
|
||||
domain/SecurityCriteria.cpp
|
||||
domain/SecurityCriteriaClause.cpp
|
||||
domain/synthetic_book_side.cpp
|
||||
domain_model/Security.cpp
|
||||
domain_model/SecurityTests.cpp
|
||||
domain_model/sod.cpp
|
||||
|
||||
@@ -64,6 +64,7 @@ function(get_cpp_sources dest)
|
||||
domain/DomainSecurityUtils.cpp
|
||||
domain/SecurityCriteria.cpp
|
||||
domain/SecurityCriteriaClause.cpp
|
||||
domain/synthetic_book_side.cpp
|
||||
domain_model/Security.cpp
|
||||
domain_model/SecurityTests.cpp
|
||||
domain_model/sod.cpp
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
OkxExeCfg {
|
||||
value{
|
||||
account : "ACC A"
|
||||
ops_id : "op_id_bts"
|
||||
secret_key : "DAF6CBB09475D2520B88D2070788A976"
|
||||
api_key : "4492a9ab-5784-454c-acc7-38dc2a503ed0"
|
||||
passphrase : "Pakistan@123"
|
||||
ws_url : "wss://wspap.okx.com:8443"
|
||||
rest_url : "https://www.okx.com"
|
||||
ws_ping_timeout_seconds : 10
|
||||
ws_ping_interval_seconds : 25
|
||||
ws_reconnect_interval_seconds : 5
|
||||
ws_reconnect_max_count : 5
|
||||
simulated_trading : true
|
||||
tdmode : "cross"
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ SymbolLists {
|
||||
list {
|
||||
key: "CBOE"
|
||||
symbol:"SPX"
|
||||
symbol:"SPXW"
|
||||
}
|
||||
list {
|
||||
key: "BINANCE_US"
|
||||
|
||||
@@ -39,8 +39,6 @@ LadderKnobs {
|
||||
security_timeout_secs: 60
|
||||
use_algo_param_grid: true
|
||||
max_mbo_orders: 200
|
||||
lighten_market_cells: true
|
||||
mimic_tt: true
|
||||
scrollable_tick_window_size: 256
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
syntax = "proto3";
|
||||
package bts.config;
|
||||
message OkxExeCfg {
|
||||
string account = 1;
|
||||
string ops_id = 2;
|
||||
string secret_key = 3;
|
||||
string api_key = 4;
|
||||
string passphrase = 5;
|
||||
string ws_url = 6;
|
||||
string rest_url = 7;
|
||||
uint32 ws_ping_timeout_seconds = 8;
|
||||
uint32 ws_ping_interval_seconds = 9;
|
||||
uint32 ws_reconnect_interval_seconds = 10;
|
||||
uint32 ws_reconnect_max_count = 11;
|
||||
bool simulated_trading = 12;
|
||||
string tdmode = 13;
|
||||
|
||||
}
|
||||
@@ -82,7 +82,7 @@ message LadderKnobs {
|
||||
bool show_scrollbar = 9;
|
||||
// DJ wants bid/ask cells with markets (i.e., size > 0) to have a
|
||||
// lighter background than those without markets.
|
||||
bool lighten_market_cells = 10;
|
||||
// bool lighten_market_cells = 10;
|
||||
|
||||
// For market cell backgrounds, 0 means 'ignore this color setting' (i.e.,
|
||||
// employ the ladder's default behavior instead).
|
||||
@@ -97,9 +97,9 @@ message LadderKnobs {
|
||||
// Pure Red : 0xFFFF0000
|
||||
// Pure Blue : 0xFF00FF00
|
||||
// Pure Green : 0xFF0000FF
|
||||
uint32 bid_market_cell_background = 11;
|
||||
uint32 ask_market_cell_background = 12;
|
||||
bool mimic_tt = 13;
|
||||
// uint32 bid_market_cell_background = 11;
|
||||
// uint32 ask_market_cell_background = 12;
|
||||
// bool mimic_tt = 13;
|
||||
// Defines the number of price rows rendered by the ladder. The smaller this
|
||||
// window is, the cheaper it is to render the ladder grid (redraw prices,
|
||||
// sizes, etc.). However, if the window is too small, scrolling may appear
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
#include "BookLevel.h"
|
||||
#include "BookLevelComp.h"
|
||||
|
||||
#include "BookSide.h"
|
||||
#include "SecurityID.h"
|
||||
#include "Side.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
|
||||
@@ -33,6 +33,11 @@ class BookLevel {
|
||||
void from_json(const nlohmann::json &j, BookLevel &bl);
|
||||
void to_json(nlohmann::json &j, const BookLevel &bl);
|
||||
|
||||
constexpr bool operator==(const BookLevel &lhs, const BookLevel &rhs);
|
||||
constexpr bool operator!=(const BookLevel &lhs, const BookLevel &rhs);
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const BookLevel &lvl) noexcept;
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
@@ -70,4 +75,29 @@ inline void to_json(nlohmann::json &j, const BookLevel &bl) {
|
||||
};
|
||||
}
|
||||
|
||||
constexpr bool operator==(const BookLevel &lhs, const BookLevel &rhs) {
|
||||
return lhs.direct_quantity == rhs.direct_quantity &&
|
||||
lhs.total_quantity == rhs.total_quantity &&
|
||||
lhs.n_orders == rhs.n_orders &&
|
||||
(lhs.price == rhs.price ||
|
||||
(lhs.price.is_nan() && rhs.price.is_nan()));
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const BookLevel &lhs, const BookLevel &rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(
|
||||
std::ostream &out, const BookLevel &lvl) noexcept {
|
||||
out << lvl.price << " (" << lvl.direct_quantity;
|
||||
if (lvl.direct_quantity != lvl.total_quantity) {
|
||||
out << "/" << lvl.total_quantity;
|
||||
}
|
||||
if (lvl.n_orders != 0) {
|
||||
out << ", " << lvl.n_orders;
|
||||
}
|
||||
out << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace bts::domain
|
||||
@@ -1,18 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "BookLevel.h"
|
||||
#include "BookLevelComp.h"
|
||||
#include "BookSide_PopulatedIterator.h"
|
||||
#include "PriceComp.h"
|
||||
#include "SecurityID.h"
|
||||
#include "Side.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
namespace bts::domain {
|
||||
@@ -37,6 +36,8 @@ class BookSideT {
|
||||
BookSideT &operator=(BookSideT &&) noexcept = default;
|
||||
~BookSideT() = default;
|
||||
|
||||
constexpr BookLevel *data();
|
||||
constexpr const BookLevel *data() const;
|
||||
[[nodiscard]] constexpr BookLevel const &top() const;
|
||||
constexpr BookLevel const &operator[](size_t i) const;
|
||||
constexpr BookLevel &operator[](size_t i);
|
||||
@@ -196,6 +197,16 @@ constexpr size_t BookSideT<SZ>::max_depth() noexcept {
|
||||
return SZ;
|
||||
}
|
||||
|
||||
template <std::size_t SZ>
|
||||
constexpr BookLevel *BookSideT<SZ>::data() {
|
||||
return _book.data();
|
||||
}
|
||||
|
||||
template <std::size_t SZ>
|
||||
constexpr const BookLevel *BookSideT<SZ>::data() const {
|
||||
return _book.data();
|
||||
}
|
||||
|
||||
template <std::size_t SZ>
|
||||
constexpr const BookLevel &BookSideT<SZ>::top() const {
|
||||
return _book[0];
|
||||
@@ -234,12 +245,14 @@ BookSideT<SZ>::end() const noexcept {
|
||||
}
|
||||
|
||||
template <std::size_t SZ>
|
||||
constexpr typename BookSideT<SZ>::ReverseIterator BookSideT<SZ>::rbegin() noexcept {
|
||||
constexpr typename BookSideT<SZ>::ReverseIterator
|
||||
BookSideT<SZ>::rbegin() noexcept {
|
||||
return _book.rbegin();
|
||||
}
|
||||
|
||||
template <std::size_t SZ>
|
||||
constexpr typename BookSideT<SZ>::ReverseIterator BookSideT<SZ>::rend() noexcept {
|
||||
constexpr typename BookSideT<SZ>::ReverseIterator
|
||||
BookSideT<SZ>::rend() noexcept {
|
||||
return _book.rend();
|
||||
}
|
||||
|
||||
@@ -250,12 +263,23 @@ constexpr size_t BookSideT<SZ>::size() const noexcept {
|
||||
|
||||
template <std::size_t SZ>
|
||||
constexpr bool BookSideT<SZ>::operator==(BookSideT const &rhs) const {
|
||||
return _book == rhs._book;
|
||||
std::size_t i = 0;
|
||||
for (; i < size(); i++) {
|
||||
const BookLevel &l = (*this)[i];
|
||||
if (i == rhs.size()) {
|
||||
return l.is_empty();
|
||||
}
|
||||
const BookLevel &r = rhs[i];
|
||||
if (l != r) {
|
||||
return l.is_empty() && r.is_empty();
|
||||
}
|
||||
}
|
||||
return i == rhs.size() || rhs[i].is_empty();
|
||||
}
|
||||
|
||||
template <std::size_t SZ>
|
||||
constexpr bool BookSideT<SZ>::operator!=(BookSideT const &rhs) const {
|
||||
return _book != rhs._book;
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
template <std::size_t SZ>
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
#include "DomainSecurityUtils.h"
|
||||
#include "FutureSecurity.h"
|
||||
#include "OptionSecurity.h"
|
||||
#include "SecurityCommon.h"
|
||||
#include "SpotSecurity.h"
|
||||
#include "SpreadSecurity.h"
|
||||
#include "lib/api/domain/Venue.pb.h"
|
||||
#include "lib/domain/TradeRecord.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/md/IntPrice.h"
|
||||
#include "lib/md/PriceSize.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
// #include "lib/domain/SecurityID.h"
|
||||
// #include "lib/utils/overloaded.h"
|
||||
// #include <fmt/format.h>
|
||||
// #include <variant>
|
||||
|
||||
namespace bts::domain {
|
||||
|
||||
@@ -62,7 +60,7 @@ bts::api::Venue DomainSecurityUtils::convert(api::domain::Venue venue) {
|
||||
case DOMAIN_VENUE(CBOE):
|
||||
return api::Venue::VENUE_CBOE;
|
||||
case DOMAIN_VENUE(TAIFEX):
|
||||
return api::Venue::VENUE_TAIFEX;
|
||||
return api::Venue::VENUE_TAIFEX;
|
||||
case DOMAIN_VENUE(FTX):
|
||||
return api::Venue::VENUE_FTX;
|
||||
case DOMAIN_VENUE(BINANCE):
|
||||
@@ -219,4 +217,76 @@ bool DomainSecurityUtils::get_security_desc(
|
||||
return true;
|
||||
}
|
||||
|
||||
domain::Price DomainSecurityUtils::convert(md::IntPrice iprice) {
|
||||
int64_t mantissa = iprice.mantissa();
|
||||
int8_t exp_diff = iprice.exponent() - domain::Price::exponent();
|
||||
if (exp_diff != 0) {
|
||||
mantissa *= std::pow(10, exp_diff);
|
||||
}
|
||||
return domain::Price{mantissa};
|
||||
}
|
||||
|
||||
BookLevel DomainSecurityUtils::convert(md::PriceSize ps) {
|
||||
BookLevel rv;
|
||||
convert(ps, rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
void DomainSecurityUtils::convert(md::PriceSize ps, BookLevel &res) {
|
||||
res.price = convert(ps.int_price());
|
||||
res.total_quantity.value() = ps.size();
|
||||
res.direct_quantity.value() = ps.direct_size();
|
||||
res.n_orders = 0;
|
||||
}
|
||||
|
||||
void DomainSecurityUtils::convert(
|
||||
const bts::domain::BookSide &book_side,
|
||||
bts::md::PriceSize *res,
|
||||
size_t res_size) {
|
||||
convert(book_side.data(), book_side.size(), res, res_size);
|
||||
}
|
||||
|
||||
void DomainSecurityUtils::convert(
|
||||
const BookLevel *book_side,
|
||||
size_t size,
|
||||
md::PriceSize *res,
|
||||
size_t res_size) {
|
||||
size_t sz = std::min(size, res_size);
|
||||
size_t i = 0;
|
||||
for (; i < sz; i++) {
|
||||
// WARNING: Implied size is discarded here. There would be data loss anyways, since
|
||||
// bts::md::PriceSize's implied size is a uint, and domain::BookLevel's is a float.
|
||||
res[i] = md::PriceSize{
|
||||
md::IntPrice{
|
||||
book_side[i].price.mantissa(), book_side[i].price.exponent()},
|
||||
book_side[i].total_quantity.value()};
|
||||
}
|
||||
// Set remaining levels to empty.
|
||||
for (; i < res_size; i++) {
|
||||
res[i] = bts::md::PriceSize{};
|
||||
}
|
||||
}
|
||||
|
||||
void DomainSecurityUtils::convert(
|
||||
const md::PriceSize *book_side, std::size_t len, BookSide &res) {
|
||||
if (res.size() < len) {
|
||||
len = res.size();
|
||||
}
|
||||
while (len-- != 0) {
|
||||
convert(book_side[len], res[len]);
|
||||
}
|
||||
}
|
||||
|
||||
void DomainSecurityUtils::convert(
|
||||
const co2::TradeMessage &tm, TradeRecord &tr) {
|
||||
tr.sec_id.value() = tm.key();
|
||||
tr.time_stamp = core::TimeStamp::from_nanos(tm.time());
|
||||
tr.agg_side = tm.aggressor_side() == co2::AggressorSide::AGGRESSOR_SIDE_BUY
|
||||
? AggressorSide::Buy
|
||||
: AggressorSide::Sell;
|
||||
tr.trade_quantity.value() = tm.size();
|
||||
tr.trade_price =
|
||||
convert(md::IntPrice{tm.mantissa(), static_cast<int8_t>(tm.exponent())});
|
||||
}
|
||||
|
||||
} // namespace bts::domain
|
||||
@@ -1,18 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "FutureSecurity.h"
|
||||
#include "OptionSecurity.h"
|
||||
#include "SpotSecurity.h"
|
||||
#include "SpreadSecurity.h"
|
||||
#include "lib/api/domain/SecurityType.pb.h"
|
||||
#include "lib/api/domain/Venue.pb.h"
|
||||
#include "lib/domain/SecurityID.h"
|
||||
#include "lib/domain/BookSide.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/utils/overloaded.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <variant>
|
||||
|
||||
namespace bts::co2 {
|
||||
class TradeMessage;
|
||||
}
|
||||
|
||||
namespace bts::md {
|
||||
class IntPrice;
|
||||
class PriceSize;
|
||||
} // namespace bts::md
|
||||
|
||||
namespace bts::domain {
|
||||
|
||||
class TradeRecord;
|
||||
|
||||
class DomainSecurityUtils {
|
||||
public:
|
||||
static api::domain::Venue convert(bts::api::Venue venue);
|
||||
@@ -29,6 +39,21 @@ class DomainSecurityUtils {
|
||||
static std::string get_exchange_id(const bts::api::Security &s);
|
||||
static bool get_security_desc(
|
||||
const bts::api::Security &security, std::string &desc);
|
||||
|
||||
static Price convert(md::IntPrice iprice);
|
||||
static BookLevel convert(md::PriceSize ps);
|
||||
static void convert(md::PriceSize ps, BookLevel &res);
|
||||
static void convert(
|
||||
const BookSide &book_side, md::PriceSize *res, std::size_t res_size);
|
||||
static void convert(
|
||||
const BookLevel *book_side,
|
||||
std::size_t size,
|
||||
md::PriceSize *res,
|
||||
std::size_t res_size);
|
||||
static void convert(
|
||||
const md::PriceSize *book_side, std::size_t len, BookSide &res);
|
||||
|
||||
static void convert(const co2::TradeMessage &tm, TradeRecord &tr);
|
||||
};
|
||||
|
||||
} // namespace bts::domain
|
||||
@@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "AccountID.h"
|
||||
#include "Price.h"
|
||||
#include "Quantity.h"
|
||||
#include "SecurityID.h"
|
||||
#include "Side.h"
|
||||
#include "TimeInForce.h"
|
||||
#include "UserID.h"
|
||||
#include "lib/domain/cme/GlbxOrderDefinition.h"
|
||||
#include "lib/domain/deribit/DeribitOrderDefinition.h"
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
|
||||
+20
-21
@@ -5,7 +5,6 @@
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/ValueWrapper.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace bts::domain {
|
||||
@@ -77,7 +76,7 @@ inline void to_json(nlohmann::json &j, const Quantity &sec_id) {
|
||||
return lhs; \
|
||||
}
|
||||
|
||||
#define QTY_UINT32_BIN_OP(result_t, op) \
|
||||
#define QTY_VALUE_BIN_OP(result_t, op) \
|
||||
constexpr result_t operator op(Quantity lhs, Quantity::Value rhs) { \
|
||||
return result_t(lhs.value() op rhs); /*NOLINT*/ \
|
||||
} \
|
||||
@@ -85,7 +84,7 @@ inline void to_json(nlohmann::json &j, const Quantity &sec_id) {
|
||||
return result_t(lhs op rhs.value()); \
|
||||
}
|
||||
|
||||
#define QTY_UINT32_BIN_REF_OP(op) \
|
||||
#define QTY_VALUE_BIN_REF_OP(op) \
|
||||
inline Quantity &operator op(Quantity &lhs, Quantity::Value rhs) { \
|
||||
lhs.value() op rhs; \
|
||||
return lhs; \
|
||||
@@ -107,29 +106,29 @@ QTY_QTY_BIN_REF_OP(*=)
|
||||
QTY_QTY_BIN_REF_OP(/=)
|
||||
// QTY_QTY_BIN_REF_OP(%=)
|
||||
|
||||
QTY_UINT32_BIN_OP(bool, !=)
|
||||
QTY_UINT32_BIN_OP(bool, ==)
|
||||
QTY_UINT32_BIN_OP(bool, <)
|
||||
QTY_UINT32_BIN_OP(bool, <=)
|
||||
QTY_UINT32_BIN_OP(bool, >)
|
||||
QTY_UINT32_BIN_OP(bool, >=)
|
||||
QTY_VALUE_BIN_OP(bool, !=)
|
||||
QTY_VALUE_BIN_OP(bool, ==)
|
||||
QTY_VALUE_BIN_OP(bool, <)
|
||||
QTY_VALUE_BIN_OP(bool, <=)
|
||||
QTY_VALUE_BIN_OP(bool, >)
|
||||
QTY_VALUE_BIN_OP(bool, >=)
|
||||
|
||||
QTY_UINT32_BIN_OP(Quantity, +)
|
||||
QTY_UINT32_BIN_OP(Quantity, -)
|
||||
QTY_UINT32_BIN_OP(Quantity, *)
|
||||
QTY_UINT32_BIN_OP(Quantity, /)
|
||||
// QTY_UINT32_BIN_OP(Quantity, %)
|
||||
QTY_VALUE_BIN_OP(Quantity, +)
|
||||
QTY_VALUE_BIN_OP(Quantity, -)
|
||||
QTY_VALUE_BIN_OP(Quantity, *)
|
||||
QTY_VALUE_BIN_OP(Quantity, /)
|
||||
// QTY_VALUE_BIN_OP(Quantity, %)
|
||||
|
||||
QTY_UINT32_BIN_REF_OP(+=)
|
||||
QTY_UINT32_BIN_REF_OP(-=)
|
||||
QTY_UINT32_BIN_REF_OP(*=)
|
||||
QTY_UINT32_BIN_REF_OP(/=)
|
||||
// QTY_UINT32_BIN_REF_OP(%=)
|
||||
QTY_VALUE_BIN_REF_OP(+=)
|
||||
QTY_VALUE_BIN_REF_OP(-=)
|
||||
QTY_VALUE_BIN_REF_OP(*=)
|
||||
QTY_VALUE_BIN_REF_OP(/=)
|
||||
// QTY_VALUE_BIN_REF_OP(%=)
|
||||
|
||||
#undef QTY_QTY_BIN_OP
|
||||
#undef QTY_QTY_BIN_REF_OP
|
||||
#undef QTY_UINT32_BIN_OP
|
||||
#undef QTY_UINT32_BIN_REF_OP
|
||||
#undef QTY_VALUE_BIN_OP
|
||||
#undef QTY_VALUE_BIN_REF_OP
|
||||
|
||||
} // namespace bts::domain
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "Security.h"
|
||||
#include "DomainSecurityUtils.h"
|
||||
#include "lib/core/TableFormatter.h"
|
||||
#include "lib/domain/FutureSecurity.h"
|
||||
#include "lib/domain/OptionSecurity.h"
|
||||
#include "lib/domain/SpotSecurity.h"
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
#pragma once
|
||||
#include "DomainSecurityUtils.h"
|
||||
#include "Price.h"
|
||||
#include "SecurityID.h"
|
||||
#include "lib/api/domain/CallPut.pb.h"
|
||||
#include "lib/api/domain/SecurityType.pb.h"
|
||||
#include "lib/api/domain/Venue.h"
|
||||
#include "lib/api/domain/Venue.pb.h"
|
||||
#include "lib/core/TimeStamp.h"
|
||||
#include "lib/domain/FutureSecurity.h"
|
||||
#include "lib/domain/LegDefinition.h"
|
||||
#include "lib/domain/OptionSecurity.h"
|
||||
#include "lib/domain/Quantity.h"
|
||||
#include "lib/domain/Side.h"
|
||||
#include "lib/domain/SpreadSecurity.h"
|
||||
#include "lib/domain/DomainSecurityUtils.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
|
||||
#include <limits>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
@@ -40,7 +30,7 @@ class SecurityCommon {
|
||||
double min_quantity_increment = 0;
|
||||
double min_quantity = 0;
|
||||
double max_quantity = std::numeric_limits<double>::max();
|
||||
double cash_per_point =1;
|
||||
double cash_per_point = 1;
|
||||
|
||||
SecurityCommon() {}
|
||||
SecurityCommon(const bts::api::Security &s)
|
||||
|
||||
@@ -21,9 +21,6 @@ using std::regex_search;
|
||||
using std::smatch;
|
||||
using std::string;
|
||||
|
||||
#define CONTAINS(container, value) \
|
||||
(std::find(container.begin(), container.end(), value) != container.end())
|
||||
|
||||
bool SecurityCriteriaClause::is_defined() const {
|
||||
return !venues.empty() || !types.empty() || !assets.empty() ||
|
||||
!groups.empty() || !ids.empty() || !names.empty() ||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "Price.h"
|
||||
#include "Quantity.h"
|
||||
#include "Security.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/domain/SecurityID.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
#include "lib/md/IntPrice.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <ostream>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace bts::domain {
|
||||
@@ -153,3 +156,5 @@ constexpr Side operator*(Side lhs, Side::Value rhs) noexcept {
|
||||
}
|
||||
|
||||
} // namespace bts::domain
|
||||
|
||||
FMT_IMPL(bts::domain::Side, s, "{}", s.name());
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "AggressorSide.h"
|
||||
#include "Price.h"
|
||||
#include "Quantity.h"
|
||||
#include "SecurityID.h"
|
||||
#include "lib/core/TimeStamp.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/IntPrice.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace bts::domain {
|
||||
@@ -18,8 +18,6 @@ class TradeRecord {
|
||||
AggressorSide agg_side = {};
|
||||
Quantity trade_quantity;
|
||||
Price trade_price;
|
||||
TradeRecord() {}
|
||||
inline TradeRecord(const bts::co2::TradeMessage &tm);
|
||||
};
|
||||
|
||||
void from_json(const nlohmann::json &j, TradeRecord &tr);
|
||||
@@ -29,16 +27,6 @@ void to_json(nlohmann::json &j, const TradeRecord &tr);
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline TradeRecord::TradeRecord(const bts::co2::TradeMessage &tm)
|
||||
: sec_id(tm.key()), time_stamp(tm.time()),
|
||||
agg_side(
|
||||
tm.aggressor_side() == co2::AggressorSide::AGGRESSOR_SIDE_BUY
|
||||
? AggressorSide{AggressorSide::Buy}
|
||||
: AggressorSide{AggressorSide::Sell}),
|
||||
trade_quantity(tm.size()),
|
||||
trade_price(Price::from_double(
|
||||
bts::md::IntPrice(tm.mantissa(), tm.exponent()).price())) {}
|
||||
|
||||
inline void from_json(const nlohmann::json &j, TradeRecord &tr) {
|
||||
io::JsonReader jr{&j};
|
||||
// clang-format off
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/domain/BookSide.h"
|
||||
#include "lib/domain/Quantity.h"
|
||||
|
||||
namespace bts::domain {
|
||||
|
||||
class BookSideConsumer {
|
||||
// Use pointer so copying is cheap
|
||||
const BookLevel *levels_;
|
||||
std::size_t size_;
|
||||
std::size_t curr_{0};
|
||||
Quantity curr_consumed_{0};
|
||||
|
||||
public:
|
||||
BookSideConsumer() = default;
|
||||
BookSideConsumer(const BookLevel *levels, std::size_t size)
|
||||
: levels_(levels), size_(size) {}
|
||||
|
||||
template <std::size_t SZ>
|
||||
BookSideConsumer(
|
||||
const BookSideT<SZ> *levels,
|
||||
std::size_t curr = 0,
|
||||
Quantity curr_consumed = Quantity{0});
|
||||
BookSideConsumer(const BookSideConsumer &other) = default;
|
||||
|
||||
BookSideConsumer &operator=(BookSideConsumer &&other) = default;
|
||||
BookSideConsumer &operator=(const BookSideConsumer &other) = default;
|
||||
|
||||
const BookLevel &operator[](std::size_t idx) const;
|
||||
|
||||
const BookLevel *levels() const { return levels_; }
|
||||
|
||||
size_t size() const;
|
||||
bool is_done() const;
|
||||
void reset();
|
||||
Quantity available(std::size_t level_limit = 1) const;
|
||||
std::pair<Quantity, Price> consume(Quantity quantity);
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
template <std::size_t SZ>
|
||||
inline BookSideConsumer::BookSideConsumer(
|
||||
const BookSideT<SZ> *levels, std::size_t curr, Quantity curr_consumed)
|
||||
: levels_(levels), curr_(curr), curr_consumed_(curr_consumed) {}
|
||||
|
||||
inline const BookLevel &BookSideConsumer::operator[](std::size_t idx) const {
|
||||
return levels_[idx];
|
||||
}
|
||||
|
||||
inline size_t BookSideConsumer::size() const { return size_; }
|
||||
|
||||
inline bool BookSideConsumer::is_done() const {
|
||||
if (curr_ >= size_) {
|
||||
return true;
|
||||
}
|
||||
if (curr_ == size_ - 1) {
|
||||
return curr_consumed_ >= levels_[curr_].total_quantity;
|
||||
}
|
||||
return curr_consumed_ >= levels_[curr_].total_quantity &&
|
||||
levels_[curr_ + 1].total_quantity == 0;
|
||||
}
|
||||
|
||||
inline void BookSideConsumer::reset() {
|
||||
curr_ = 0;
|
||||
curr_consumed_.value() = 0;
|
||||
}
|
||||
|
||||
inline Quantity BookSideConsumer::available(std::size_t level_limit) const {
|
||||
std::size_t end = size_ > curr_ + level_limit ? curr_ + level_limit : size_;
|
||||
Quantity rv{0};
|
||||
for (std::size_t i = curr_; i < end; i++) {
|
||||
Quantity level_qty = levels_[i].total_quantity;
|
||||
if (i == curr_) {
|
||||
level_qty -= curr_consumed_;
|
||||
}
|
||||
rv += level_qty;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
inline std::pair<Quantity, Price> BookSideConsumer::consume(Quantity quantity) {
|
||||
Quantity consumed{0};
|
||||
Price price = Price::zero();
|
||||
for (; curr_ < size_; curr_++) {
|
||||
Quantity level_qty = levels_[curr_].total_quantity - curr_consumed_;
|
||||
if (level_qty == 0) {
|
||||
break;
|
||||
}
|
||||
Quantity rem = quantity - consumed;
|
||||
if (level_qty > rem) {
|
||||
curr_consumed_ += rem;
|
||||
consumed = quantity;
|
||||
price += levels_[curr_].price * rem.value();
|
||||
break;
|
||||
}
|
||||
curr_consumed_.value() = 0;
|
||||
consumed += level_qty;
|
||||
price += levels_[curr_].price * level_qty.value();
|
||||
}
|
||||
return {consumed, price};
|
||||
}
|
||||
|
||||
} // namespace bts::domain
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "synthetic_book_side.h"
|
||||
#include "lib/domain/book_side_consumer.h"
|
||||
|
||||
namespace bts::domain {
|
||||
|
||||
void SyntheticBookSide::copy_(const SyntheticBookSide &other) noexcept {
|
||||
if (this == &other) {
|
||||
return;
|
||||
}
|
||||
leg_qty_.value() = other.leg_qty_.value();
|
||||
// Copy 'data_'
|
||||
if (other.data_ != nullptr) {
|
||||
// Clear 'own_data_'
|
||||
if (own_data_ != nullptr) {
|
||||
delete[] own_data_;
|
||||
own_data_ = nullptr;
|
||||
}
|
||||
size_ = other.size_;
|
||||
data_ = other.data_;
|
||||
return;
|
||||
}
|
||||
data_ = nullptr;
|
||||
if (other.size_ == 0) {
|
||||
// Clear 'own_data_'
|
||||
if (own_data_ != nullptr) {
|
||||
delete[] own_data_;
|
||||
own_data_ = nullptr;
|
||||
}
|
||||
size_ = 0;
|
||||
return;
|
||||
}
|
||||
// Copy 'own_data_'
|
||||
if (own_data_ == nullptr) {
|
||||
own_data_ = new BookLevel[other.size_];
|
||||
} else if (size_ != other.size_) {
|
||||
delete[] own_data_;
|
||||
own_data_ = new BookLevel[other.size_];
|
||||
}
|
||||
size_ = other.size_;
|
||||
std::memcpy(own_data_, other.own_data_, size_ * sizeof(BookLevel));
|
||||
}
|
||||
|
||||
void SyntheticBookSide::move_(SyntheticBookSide &&other) noexcept {
|
||||
if (own_data_ != nullptr) {
|
||||
delete[] own_data_;
|
||||
}
|
||||
leg_qty_.value() = other.leg_qty_.value();
|
||||
data_ = other.data_;
|
||||
size_ = other.size_;
|
||||
own_data_ = other.own_data_;
|
||||
other.leg_qty_.value() = 0;
|
||||
other.data_ = nullptr;
|
||||
other.own_data_ = nullptr;
|
||||
other.size_ = 0;
|
||||
}
|
||||
|
||||
SyntheticBookSide SyntheticBookSide::add_leg(
|
||||
SyntheticBookSide other_leg,
|
||||
BookLevel *synth_bk,
|
||||
size_t synth_bk_size) const noexcept {
|
||||
if (leg_qty_.value() == 0) {
|
||||
return other_leg;
|
||||
}
|
||||
if (other_leg.leg_qty_.value() == 0) {
|
||||
return *this;
|
||||
}
|
||||
SyntheticBookSide rv;
|
||||
rv.leg_qty_.value() = 1;
|
||||
rv.size_ = synth_bk_size;
|
||||
if (synth_bk == nullptr) {
|
||||
rv.own_data_ = synth_bk = new BookLevel[synth_bk_size];
|
||||
} else {
|
||||
rv.data_ = synth_bk;
|
||||
}
|
||||
BookSideConsumer l1{data(), size_};
|
||||
BookSideConsumer l2{other_leg.data(), other_leg.size_};
|
||||
Quantity l1_qty = leg_qty_;
|
||||
Quantity l2_qty = other_leg.leg_qty_;
|
||||
std::size_t i = 0;
|
||||
for (; !l1.is_done() && !l2.is_done() && i < synth_bk_size; i++) {
|
||||
Quantity leg2_per_leg1 = (l2_qty / l1_qty);
|
||||
if (leg2_per_leg1 < 0) {
|
||||
leg2_per_leg1.value() = -leg2_per_leg1.value();
|
||||
}
|
||||
Quantity leg1_per_leg2 = (l1_qty / l2_qty);
|
||||
if (leg1_per_leg2 < 0) {
|
||||
leg1_per_leg2.value() = -leg1_per_leg2.value();
|
||||
}
|
||||
Quantity avail1 = l1.available();
|
||||
Quantity avail2 = l2.available();
|
||||
Quantity take1;
|
||||
Quantity take2;
|
||||
if ((avail1 / leg1_per_leg2) < (avail2 / leg2_per_leg1)) {
|
||||
take1 = avail1;
|
||||
take2 = avail1 * leg2_per_leg1;
|
||||
} else {
|
||||
take1 = avail2 * leg1_per_leg2;
|
||||
take2 = avail2;
|
||||
}
|
||||
const auto [qty1, cost1] = l1.consume(take1);
|
||||
const auto [qty2, cost2] = l2.consume(take2);
|
||||
Price price1 = (cost1 / qty1.value()) * l1_qty.value();
|
||||
Price price2 = (cost2 / qty2.value()) * l2_qty.value();
|
||||
auto &synth_lvl = synth_bk[i];
|
||||
synth_lvl.price = price1 + price2;
|
||||
synth_lvl.total_quantity = qty1;
|
||||
synth_lvl.direct_quantity = qty1;
|
||||
synth_lvl.n_orders = 0;
|
||||
}
|
||||
for (; i < synth_bk_size; i++) {
|
||||
synth_bk[i] = BookLevel::empty();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // namespace bts::domain
|
||||
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/domain/BookLevel.h"
|
||||
#include "lib/domain/Quantity.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
namespace bts::domain {
|
||||
|
||||
class SyntheticBookSide {
|
||||
Quantity leg_qty_{0};
|
||||
// Either 'data_' or 'own_data_' can be non-null (not both). Only the latter
|
||||
// is deleted in the destructor.
|
||||
const BookLevel *data_{nullptr};
|
||||
BookLevel *own_data_{nullptr};
|
||||
std::size_t size_{0};
|
||||
|
||||
void copy_(const SyntheticBookSide &other) noexcept;
|
||||
void move_(SyntheticBookSide &&other) noexcept;
|
||||
|
||||
public:
|
||||
constexpr SyntheticBookSide() = default;
|
||||
constexpr SyntheticBookSide(
|
||||
Quantity leg_qty, const BookLevel *book_side, std::size_t size);
|
||||
constexpr ~SyntheticBookSide();
|
||||
|
||||
SyntheticBookSide(const SyntheticBookSide &other);
|
||||
SyntheticBookSide(SyntheticBookSide &&other);
|
||||
|
||||
SyntheticBookSide &operator=(const SyntheticBookSide &other);
|
||||
SyntheticBookSide &operator=(SyntheticBookSide &&other);
|
||||
|
||||
constexpr std::size_t size() const;
|
||||
constexpr const BookLevel *data() const;
|
||||
|
||||
/**
|
||||
* Adds a leg to this one (calls can be chained to get the depth for N legs).
|
||||
*
|
||||
* @return SyntheticBookSide whose levels constitute the synthetic depth of
|
||||
* this and the specified @p leg. Its quantity is always 1.
|
||||
*/
|
||||
SyntheticBookSide add_leg(SyntheticBookSide leg) const noexcept;
|
||||
SyntheticBookSide add_leg(
|
||||
SyntheticBookSide other_leg,
|
||||
BookLevel *synth_bk,
|
||||
size_t synth_bk_size) const noexcept;
|
||||
|
||||
/**
|
||||
* Syntactic sugar for `add_leg`
|
||||
*/
|
||||
SyntheticBookSide operator+(SyntheticBookSide other) const noexcept;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
constexpr SyntheticBookSide::SyntheticBookSide(
|
||||
Quantity leg_qty, const BookLevel *data, std::size_t size)
|
||||
: leg_qty_(leg_qty), data_(data), own_data_(nullptr), size_(size) {}
|
||||
|
||||
constexpr SyntheticBookSide::~SyntheticBookSide() {
|
||||
leg_qty_.value() = 0;
|
||||
data_ = nullptr;
|
||||
if (own_data_ != nullptr) {
|
||||
delete[] own_data_;
|
||||
own_data_ = nullptr;
|
||||
}
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
inline SyntheticBookSide::SyntheticBookSide(const SyntheticBookSide &other) {
|
||||
copy_(other);
|
||||
}
|
||||
|
||||
inline SyntheticBookSide::SyntheticBookSide(SyntheticBookSide &&other) {
|
||||
move_(std::move(other));
|
||||
}
|
||||
|
||||
inline SyntheticBookSide &SyntheticBookSide::operator=(
|
||||
const SyntheticBookSide &other) {
|
||||
if (this != &other) {
|
||||
copy_(other);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline SyntheticBookSide &SyntheticBookSide::operator=(
|
||||
SyntheticBookSide &&other) {
|
||||
if (this != &other) {
|
||||
move_(std::move(other));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr std::size_t SyntheticBookSide::size() const { return size_; }
|
||||
|
||||
constexpr const BookLevel *SyntheticBookSide::data() const {
|
||||
return data_ == nullptr ? own_data_ : data_;
|
||||
}
|
||||
|
||||
inline SyntheticBookSide SyntheticBookSide::add_leg(
|
||||
SyntheticBookSide other_leg) const noexcept {
|
||||
return add_leg(other_leg, nullptr, 10);
|
||||
}
|
||||
inline SyntheticBookSide SyntheticBookSide ::operator+(
|
||||
SyntheticBookSide other) const noexcept {
|
||||
return add_leg(other);
|
||||
}
|
||||
|
||||
} // namespace bts::domain
|
||||
@@ -1,10 +1,8 @@
|
||||
#include "Security.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
|
||||
#include "lib/config/config.h"
|
||||
#include "lib/config/protos/orders_fills_cleanup.pb.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/md/common.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <google/protobuf/text_format.h>
|
||||
@@ -13,7 +11,6 @@ namespace bts {
|
||||
namespace securities {
|
||||
|
||||
using bts::api::Security;
|
||||
using bts::api::Venue;
|
||||
using std::optional;
|
||||
|
||||
api::SecurityType security_type(api::Security const &security) {
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
#include "lib/domain_model/Venue.h"
|
||||
#include "lib/domain_model/common.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
namespace boost {
|
||||
namespace gregorian {
|
||||
|
||||
@@ -205,10 +205,20 @@ message SyntheticContract {
|
||||
enum PriceCalc {
|
||||
PRICECALC_STANDARD = 0;
|
||||
PRICECALC_NET_CHANGE = 1;
|
||||
PRICECALC_NOTIONAL_LEG = 2;
|
||||
// RATIO = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ContractQuantityInfo {
|
||||
double min_value = 1;
|
||||
double max_value = 2;
|
||||
double tick_unit = 3;
|
||||
double tick_base = 4;
|
||||
double multiplier = 5;
|
||||
double max_price = 6;
|
||||
}
|
||||
|
||||
message ContractSpecification {
|
||||
double low_tick = 10;
|
||||
double price_break = 20;
|
||||
@@ -219,6 +229,8 @@ message ContractSpecification {
|
||||
uint32 main_fraction = 60;
|
||||
uint32 sub_fraction = 61;
|
||||
uint32 price_display_format = 62;
|
||||
repeated ContractQuantityInfo quantity_infos = 63;
|
||||
repeated ContractQuantityInfo notional_quantity_infos = 64;
|
||||
}
|
||||
|
||||
message Security {
|
||||
|
||||
@@ -34,7 +34,31 @@ static size_t write_headers(
|
||||
void *ptr, size_t size, size_t nmemb, IHttpResponse *data) {
|
||||
return data->write_headers((char *)ptr, size, nmemb);
|
||||
}
|
||||
|
||||
// static int curl_debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr) {
|
||||
// (void)handle;
|
||||
// (void)type;
|
||||
// (void)userptr;
|
||||
// switch (type) {
|
||||
// case CURLINFO_TEXT:
|
||||
// LOG(Http, Info, "CURLINFO_TEXT: {}", std::string(data, size));
|
||||
// break;
|
||||
// case CURLINFO_HEADER_IN:
|
||||
// LOG(Http, Info, "Header In: {}", std::string(data, size));
|
||||
// break;
|
||||
// case CURLINFO_HEADER_OUT:
|
||||
// LOG(Http, Info, "Header Out: {}", std::string(data, size));
|
||||
// break;
|
||||
// case CURLINFO_DATA_IN:
|
||||
// LOG(Http, Info, "Data In: {} bytes, in: {}", size,std::string(data, size));
|
||||
// break;
|
||||
// case CURLINFO_DATA_OUT:
|
||||
// LOG(Http, Info, "Data Out: {} bytes, out: {}", size, std::string(data, size));
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// return 0;
|
||||
// }
|
||||
void HttpClient::get(
|
||||
string url,
|
||||
unordered_map<string, string> headers,
|
||||
@@ -91,6 +115,8 @@ void HttpClient::send(const HttpRequest &req, IHttpResponse *response) {
|
||||
CURL *curl = session.curl;
|
||||
CurlHeaders curl_headers{req.headers};
|
||||
// Prepare req
|
||||
//to debug curl requests
|
||||
// curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers.data());
|
||||
@@ -108,6 +134,7 @@ void HttpClient::send(const HttpRequest &req, IHttpResponse *response) {
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPPOST, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req.body.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, req.body.size());
|
||||
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
break;
|
||||
case io::http::HttpMethod::PUT:
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "lib/io/websocket/websocket_error_code.h"
|
||||
#include "lib/io/websocket/websocketpp_exception_formatter.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/formatters/exception_formatter.h"
|
||||
|
||||
#include <atomic>
|
||||
@@ -201,9 +202,10 @@ struct WebsocketClient::PImpl {
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
std::string str = ex.what();
|
||||
ERROR_FN(
|
||||
"connect_no_timeout. Failed to connect. {}; err: {}", snapshot(),
|
||||
ex);
|
||||
str);
|
||||
set_state(State::IDLE);
|
||||
// Don't wait for connect
|
||||
return;
|
||||
@@ -236,11 +238,16 @@ struct WebsocketClient::PImpl {
|
||||
ERROR_FN(
|
||||
"io_thread. Websocket io threw websocketpp::exception. {}; "
|
||||
"err: {}",
|
||||
snapshot(), ex);
|
||||
snapshot(), std::string(ex.what()));
|
||||
bts_assert_msgf(false, "io_thread. Websocket io threw websocketpp::exception. {}; "
|
||||
"err: {}",
|
||||
snapshot(), std::string(ex.what()));
|
||||
} catch (const std::exception &ex) {
|
||||
ERROR_FN(
|
||||
"io_thread. Websocket io threw std::exception. {}; err: {}",
|
||||
snapshot(), ex);
|
||||
snapshot(), std::string(ex.what()));
|
||||
bts_assert_msgf(false, "io_thread. Websocket io threw std::exception. {}; err: {}",
|
||||
snapshot(), std::string(ex.what()));
|
||||
} catch (...) {
|
||||
bts_assert_msgf(
|
||||
false,
|
||||
@@ -300,6 +307,7 @@ struct WebsocketClient::PImpl {
|
||||
const auto ec = client.get_con_from_hdl(hdl)->get_ec();
|
||||
if (ec) {
|
||||
ERROR_FN("on_open. {}; err: {}", snapshot(), ec.message());
|
||||
bts_assert(false);
|
||||
}
|
||||
switch (state.value) {
|
||||
case State::OPENING:
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <cstdint>
|
||||
#include <fmt/ostream.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
@@ -94,7 +95,7 @@ std::ifstream::pos_type filesize(std::string const &filename) {
|
||||
Sink file_sink(LoggingConfig const &cfg) {
|
||||
static std::ofstream stream;
|
||||
static uint64_t bytes_written = 0;
|
||||
const auto max_sz = 120 * 1024 * 1024;
|
||||
const uintmax_t max_sz = 1024 * 1024 * 1024 ;
|
||||
|
||||
auto f = formatter(cfg);
|
||||
auto path = log_path();
|
||||
@@ -107,7 +108,7 @@ Sink file_sink(LoggingConfig const &cfg) {
|
||||
|
||||
if (boost::filesystem::exists(path)) {
|
||||
auto sz = boost::filesystem::file_size(path);
|
||||
if (sz > max_sz && false) {
|
||||
if (sz > max_sz) {
|
||||
auto backup = boost::filesystem::path(path.string() + ".old");
|
||||
boost::filesystem::rename(path, backup);
|
||||
}
|
||||
@@ -118,8 +119,7 @@ Sink file_sink(LoggingConfig const &cfg) {
|
||||
return [=](LogMsg const &m) {
|
||||
auto msg = f(m);
|
||||
bytes_written += msg.size();
|
||||
if (bytes_written >= max_sz && false) {
|
||||
return;
|
||||
if (bytes_written >= max_sz) {
|
||||
bytes_written = 0;
|
||||
stream.close();
|
||||
auto backup = boost::filesystem::path(path.string() + ".old");
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
//#ifdef SPARK_ENABLE_CCAPI
|
||||
#include "GeneralMarketDataListener.h"
|
||||
#include "lib/api/domain/Currency.pb.h"
|
||||
#include "lib/api/domain/SecurityType.pb.h"
|
||||
#include "lib/api/domain/Venue.pb.h"
|
||||
#include "lib/core/TimeStamp.h"
|
||||
@@ -21,7 +19,6 @@
|
||||
#include "lib/domain/SpreadSecurity.h"
|
||||
#include "lib/domain/TradeRecord.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/domain_model/common.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/GeneralSecurityIndex.h"
|
||||
#include "lib/md/IMarketDataListener.h"
|
||||
@@ -29,28 +26,20 @@
|
||||
#include "lib/md/MDProducer.h"
|
||||
#include "lib/md/MDProducerHandle.h"
|
||||
#include "lib/md/PriceSize.h"
|
||||
#include "lib/md/SecurityIndex.h"
|
||||
#include "lib/md/Shm/FixedSecurity.h"
|
||||
#include "lib/md/Shm/MDStructure.h"
|
||||
#include "lib/md/protos/co2.pb.h"
|
||||
#include "lib/orderman/actors/SODPosition.h"
|
||||
#include "lib/orderman/gateways/sim/SimExecutionGateway.h"
|
||||
#include "lib/orderman/gateways/sim/SimExecutionGatewayFactory.h"
|
||||
#include "lib/orderman/orderman_utils/Book.h"
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/persistence/IDGenerator.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "lib/utils/TimeStamp.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/bts_fatal.h"
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
||||
#include <bits/stdint-uintn.h>
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
#include <google/protobuf/timestamp.pb.h>
|
||||
#include <google/protobuf/util/time_util.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -80,74 +69,88 @@ struct CCSecSearchResult {
|
||||
struct IncrementalBookData {
|
||||
std::map<domain::SecurityID, domain::Book> book_map;
|
||||
|
||||
bool update_incremental(const IMarketDataListener::BookOp &op) {
|
||||
//DONT NEED THIS?
|
||||
// Validate inputs first
|
||||
if (op.price <= 0.0 || op.quantity < 0) {
|
||||
LOG(md, Error, "Invalid input - Price: {}, Quantity: {}", op.price, op.quantity);
|
||||
return false;
|
||||
}
|
||||
bool update_incremental(const IMarketDataListener::BookOp &op) {
|
||||
//DONT NEED THIS?
|
||||
// Validate inputs first
|
||||
if (op.price <= 0.0 || op.quantity < 0) {
|
||||
LOG(md, Error, "Invalid input - Price: {}, Quantity: {}", op.price,
|
||||
op.quantity);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Logging setup
|
||||
//DO WE NEED TO PUT THESE IN A STRING
|
||||
std::string side_str = (op.side == domain::Side::Buy) ? "Buy" : "Sell";
|
||||
std::string op_type_str;
|
||||
switch (op.op_type) {
|
||||
case IMarketDataListener::BookOpType::Add: op_type_str = "Add"; break;
|
||||
case IMarketDataListener::BookOpType::MODIFY: op_type_str = "Modify"; break;
|
||||
case IMarketDataListener::BookOpType::REMOVE: op_type_str = "Delete"; break;
|
||||
default: op_type_str = "Unknown"; break;
|
||||
}
|
||||
//CAN WE DO THIS WITHOUT ASSIGNING TO STRINGS
|
||||
std::stringstream op_details;
|
||||
op_details << "Operation: Price: " << op.price << ", Quantity: " << op.quantity
|
||||
<< ", Side: " << side_str << ", Type: " << op_type_str << "\n";
|
||||
// Logging setup
|
||||
//DO WE NEED TO PUT THESE IN A STRING
|
||||
std::string side_str = (op.side == domain::Side::Buy) ? "Buy" : "Sell";
|
||||
std::string op_type_str;
|
||||
switch (op.op_type) {
|
||||
case IMarketDataListener::BookOpType::Add:
|
||||
op_type_str = "Add";
|
||||
break;
|
||||
case IMarketDataListener::BookOpType::MODIFY:
|
||||
op_type_str = "Modify";
|
||||
break;
|
||||
case IMarketDataListener::BookOpType::REMOVE:
|
||||
op_type_str = "Delete";
|
||||
break;
|
||||
default:
|
||||
op_type_str = "Unknown";
|
||||
break;
|
||||
}
|
||||
//CAN WE DO THIS WITHOUT ASSIGNING TO STRINGS
|
||||
std::stringstream op_details;
|
||||
op_details << "Operation: Price: " << op.price
|
||||
<< ", Quantity: " << op.quantity << ", Side: " << side_str
|
||||
<< ", Type: " << op_type_str << "\n";
|
||||
|
||||
LOG(md, Info, "Book Increment: {}", op_details.str());
|
||||
LOG(md, Info, "Book Increment: {}", op_details.str());
|
||||
|
||||
// Get book and validate
|
||||
auto book = get_book(op.id);
|
||||
if (book == nullptr) {
|
||||
LOG(md, Error, "Book not found for id: {}", op.id);
|
||||
return false;
|
||||
}
|
||||
// Get book and validate
|
||||
auto book = get_book(op.id);
|
||||
if (book == nullptr) {
|
||||
LOG(md, Error, "Book not found for id: {}", op.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get correct side of the book
|
||||
auto& book_side = (op.side == domain::Side::Buy) ? book->bids() : book->asks();
|
||||
// Get correct side of the book
|
||||
auto &book_side =
|
||||
(op.side == domain::Side::Buy) ? book->bids() : book->asks();
|
||||
|
||||
// Log initial state
|
||||
std::stringstream book_before;
|
||||
book_before << "Book before update (" << side_str << " side):\n";
|
||||
for (const auto& level : book_side) {
|
||||
book_before << "Price: " << level.price << ", Quantity: " << level.total_quantity << "\n";
|
||||
}
|
||||
// Log initial state
|
||||
std::stringstream book_before;
|
||||
book_before << "Book before update (" << side_str << " side):\n";
|
||||
for (const auto &level : book_side) {
|
||||
book_before << "Price: " << level.price
|
||||
<< ", Quantity: " << level.total_quantity << "\n";
|
||||
}
|
||||
|
||||
/*-----------*/
|
||||
if(op.side == domain::Side::Buy){
|
||||
switch(op.op_type){
|
||||
case IMarketDataListener::BookOpType::Add:
|
||||
{
|
||||
/*-----------*/
|
||||
if (op.side == domain::Side::Buy) {
|
||||
switch (op.op_type) {
|
||||
case IMarketDataListener::BookOpType::Add: {
|
||||
int insert_position = 0;
|
||||
for (size_t i = 0; i < book_side.size(); i++){//not sure how to access book
|
||||
if(book_side[i].price != 0 && book_side[i].price > op.price ){
|
||||
for (size_t i = 0; i < book_side.size();
|
||||
i++) { //not sure how to access book
|
||||
if (book_side[i].price != 0 && book_side[i].price > op.price) {
|
||||
//book_side is the book we have ?
|
||||
//op.price is what we received ?
|
||||
insert_position++;
|
||||
continue;
|
||||
}else {//need j to be last level
|
||||
for (size_t j =fmin(book_side.size()-1,5-1); j > i; j--){
|
||||
book_side[j] = book_side[j-1];
|
||||
} else { //need j to be last level
|
||||
for (size_t j = fmin(book_side.size() - 1, 5 - 1); j > i;
|
||||
j--) {
|
||||
book_side[j] = book_side[j - 1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
book_side[insert_position].price = op.price;
|
||||
book_side[insert_position].total_quantity = op.quantity;//dont know how to add quantity
|
||||
book_side[insert_position].total_quantity =
|
||||
op.quantity; //dont know how to add quantity
|
||||
break;
|
||||
}
|
||||
case IMarketDataListener::BookOpType::MODIFY:{
|
||||
for(size_t i = 0; i < fmin(book_side.size(),5); i++){
|
||||
if(book_side[i].price != 0 && book_side[i].price == op.price){
|
||||
case IMarketDataListener::BookOpType::MODIFY: {
|
||||
for (size_t i = 0; i < fmin(book_side.size(), 5); i++) {
|
||||
if (book_side[i].price != 0 && book_side[i].price == op.price) {
|
||||
//want to update quantity if price is a match
|
||||
book_side[i].total_quantity = op.quantity;
|
||||
break;
|
||||
@@ -155,103 +158,113 @@ bool update_incremental(const IMarketDataListener::BookOp &op) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IMarketDataListener::BookOpType::REMOVE:{
|
||||
case IMarketDataListener::BookOpType::REMOVE: {
|
||||
int found_position = -1;
|
||||
for (size_t i = 0; i < fmin(book_side.size(),5); i++){
|
||||
if(book_side[i].price == op.price){//trying to find based on price only
|
||||
for (size_t i = 0; i < fmin(book_side.size(), 5); i++) {
|
||||
if (book_side[i].price ==
|
||||
op.price) { //trying to find based on price only
|
||||
found_position = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
size_t level = found_position;
|
||||
//would've had 4 here; want to be at last level
|
||||
for(size_t i = found_position; i <fmin(book_side.size(),5); i++){
|
||||
book_side[i] = book_side[i+1];
|
||||
//would've had 4 here; want to be at last level
|
||||
for (size_t i = found_position; i < fmin(book_side.size(), 5);
|
||||
i++) {
|
||||
book_side[i] = book_side[i + 1];
|
||||
level++;
|
||||
}
|
||||
if(found_position != -1){
|
||||
if (found_position != -1) {
|
||||
//trying to set last level to 0
|
||||
book_side[level].total_quantity =domain::Quantity(0);
|
||||
book_side[level].total_quantity = domain::Quantity(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG(md,Error, "In the Default Case for IMarketDataListener::BookOp {}");
|
||||
}
|
||||
}else if (op.side == domain::Side::Sell){
|
||||
switch(op.op_type){
|
||||
case IMarketDataListener::BookOpType::Add:{
|
||||
LOG(md, Error,
|
||||
"In the Default Case for IMarketDataListener::BookOp {}");
|
||||
}
|
||||
} else if (op.side == domain::Side::Sell) {
|
||||
switch (op.op_type) {
|
||||
case IMarketDataListener::BookOpType::Add: {
|
||||
int insert_position = 0;
|
||||
for (int i = 0; i < fmin(book_side.size(),5); i++) {
|
||||
if (book_side[i].price != 0 && book_side[i].price < op.price) {
|
||||
insert_position++;
|
||||
continue;
|
||||
}
|
||||
}//need i to be at the last level
|
||||
for (int i = fmin(book_side.size()-1,5-1); i > insert_position; i--) {
|
||||
book_side[i].price = book_side[i - 1].price;
|
||||
book_side[i].total_quantity = book_side[i - 1].total_quantity;
|
||||
for (int i = 0; i < fmin(book_side.size(), 5); i++) {
|
||||
if (book_side[i].price != 0 && book_side[i].price < op.price) {
|
||||
insert_position++;
|
||||
continue;
|
||||
}
|
||||
} //need i to be at the last level
|
||||
for (int i = fmin(book_side.size() - 1, 5 - 1); i > insert_position;
|
||||
i--) {
|
||||
book_side[i].price = book_side[i - 1].price;
|
||||
book_side[i].total_quantity = book_side[i - 1].total_quantity;
|
||||
}
|
||||
book_side[insert_position].price = op.price;
|
||||
book_side[insert_position].total_quantity = op.quantity;
|
||||
book_side[insert_position].total_quantity = op.quantity;
|
||||
break;
|
||||
}
|
||||
case IMarketDataListener::BookOpType::MODIFY:{
|
||||
for (int i = 0; i < fmin(book_side.size(),5); i++) {
|
||||
if (book_side[i].price != 0 && book_side[i].price == op.price) {
|
||||
// For modify, we might want to update or keep the same value
|
||||
book_side[i].price = op.price;
|
||||
book_side[i].total_quantity = op.quantity;
|
||||
break;
|
||||
}
|
||||
}break;
|
||||
case IMarketDataListener::BookOpType::MODIFY: {
|
||||
for (int i = 0; i < fmin(book_side.size(), 5); i++) {
|
||||
if (book_side[i].price != 0 && book_side[i].price == op.price) {
|
||||
// For modify, we might want to update or keep the same value
|
||||
book_side[i].price = op.price;
|
||||
book_side[i].total_quantity = op.quantity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IMarketDataListener::BookOpType::REMOVE: {
|
||||
int found_position = -1;
|
||||
for (size_t i = 0; i < fmin(book_side.size(),5); i++){
|
||||
if(book_side[i].price == op.price){
|
||||
for (size_t i = 0; i < fmin(book_side.size(), 5); i++) {
|
||||
if (book_side[i].price == op.price) {
|
||||
found_position = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
size_t level =found_position;
|
||||
for (int i = found_position; i < fmin(book_side.size(),5); i++){
|
||||
book_side[i].price = book_side[i+1].price;
|
||||
book_side[i].total_quantity = book_side[i+1].total_quantity;
|
||||
size_t level = found_position;
|
||||
for (int i = found_position; i < fmin(book_side.size(), 5); i++) {
|
||||
book_side[i].price = book_side[i + 1].price;
|
||||
book_side[i].total_quantity = book_side[i + 1].total_quantity;
|
||||
level++;
|
||||
}
|
||||
if(found_position != -1){
|
||||
book_side[fmin(book_side.size()-1,level)].total_quantity = domain::Quantity(0);
|
||||
if (found_position != -1) {
|
||||
book_side[fmin(book_side.size() - 1, level)].total_quantity =
|
||||
domain::Quantity(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG(md, Error, "In the Default Case for IMarketDataListener::BookOp {}");
|
||||
LOG(md, Error,
|
||||
"In the Default Case for IMarketDataListener::BookOp {}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*---------------------*/
|
||||
int counter = 0;
|
||||
for (auto &level : book_side) {
|
||||
if (counter > 4) {
|
||||
level.total_quantity = domain::Quantity(0);
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
/*---------------------*/
|
||||
int counter =0;
|
||||
for(auto &level:book_side){
|
||||
if(counter >4){
|
||||
level.total_quantity =domain::Quantity(0);
|
||||
|
||||
// Log final state
|
||||
std::stringstream book_after;
|
||||
book_after << "Book after update (" << side_str << " side):\n";
|
||||
for (const auto &level : book_side) {
|
||||
book_after << "Price: " << level.price
|
||||
<< ", Quantity: " << level.total_quantity << "\n";
|
||||
}
|
||||
counter++;
|
||||
|
||||
std::string book_before_after_op =
|
||||
book_before.str() + "\n" + book_after.str() + "\n" + op_details.str();
|
||||
LOG(md, Diag, "Book Increment desc: {}", book_before_after_op);
|
||||
bts_assert_msgf(
|
||||
book->is_sorted(), "book not sorted {}", book_before_after_op);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Log final state
|
||||
std::stringstream book_after;
|
||||
book_after << "Book after update (" << side_str << " side):\n";
|
||||
for (const auto& level : book_side) {
|
||||
book_after << "Price: " << level.price << ", Quantity: " << level.total_quantity << "\n";
|
||||
}
|
||||
|
||||
std::string book_before_after_op = book_before.str() + "\n" + book_after.str() + "\n" + op_details.str();
|
||||
LOG(md, Diag, "Book Increment desc: {}", book_before_after_op);
|
||||
bts_assert_msgf(book->is_sorted(), "book not sorted {}", book_before_after_op);
|
||||
|
||||
return true;
|
||||
}
|
||||
domain::Book const *get_const_book(domain::SecurityID id) const {
|
||||
auto it = book_map.find(id);
|
||||
if (it == book_map.end()) {
|
||||
@@ -301,8 +314,7 @@ struct GeneralMarketDataListener::PImpl {
|
||||
GeneralSecurityIndex &index,
|
||||
MDProducer &md_producer,
|
||||
std::shared_ptr<bts::orderman::SimExecutionGateway> sim_ex_gwy)
|
||||
: venue(venue), index(index),
|
||||
md_producer(md_producer),
|
||||
: venue(venue), index(index), md_producer(md_producer),
|
||||
sim_ex_gwy(std::move(sim_ex_gwy)) {
|
||||
if (this->sim_ex_gwy != nullptr) {
|
||||
this->sim_ex_gwy->set_producers(&this->index);
|
||||
@@ -464,7 +476,7 @@ void GeneralMarketDataListener::on_book(
|
||||
|
||||
void GeneralMarketDataListener::on_book_increment(
|
||||
const IMarketDataListener::BookOp &book_op) {
|
||||
if(!me->inc_book_data.update_incremental(book_op)){
|
||||
if (!me->inc_book_data.update_incremental(book_op)) {
|
||||
return;
|
||||
}
|
||||
domain::Book const *book_ptr = me->inc_book_data.get_const_book(book_op.id);
|
||||
@@ -635,10 +647,99 @@ void GeneralMarketDataListener::_commit_security(const FixedSecurity &sec) {
|
||||
handle.security().numeric_id, me->index.n_secs());
|
||||
}
|
||||
|
||||
static bool get_qty_info(
|
||||
double qty_incr, float &qty_tick_unit, float &qty_tick_base) {
|
||||
float qty_tick_denom = 1.0 / qty_incr;
|
||||
int qty_tick_denomi = static_cast<int>(qty_tick_denom);
|
||||
if (qty_tick_denom != qty_tick_denomi) {
|
||||
return false;
|
||||
}
|
||||
switch (static_cast<int>(qty_tick_denom)) {
|
||||
case 1:
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 1;
|
||||
break;
|
||||
case 10:
|
||||
qty_tick_unit = 10;
|
||||
qty_tick_base = 100;
|
||||
break;
|
||||
case 100:
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 100;
|
||||
break;
|
||||
case 1000:
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 1000;
|
||||
break;
|
||||
case 10000:
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 10000;
|
||||
break;
|
||||
case 100000:
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 100000;
|
||||
break;
|
||||
case 1000000:
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 1000000;
|
||||
break;
|
||||
case 10'000'000:
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 10'000'000;
|
||||
break;
|
||||
case 100'000'000:
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 100'000'000;
|
||||
break;
|
||||
case 1'000'000'000:
|
||||
qty_tick_unit = 1;
|
||||
qty_tick_base = 1'000'000'000;
|
||||
break;
|
||||
case 4:
|
||||
qty_tick_unit = 25;
|
||||
qty_tick_base = 100;
|
||||
break;
|
||||
case 40:
|
||||
qty_tick_unit = 25;
|
||||
qty_tick_base = 1000;
|
||||
break;
|
||||
case 400:
|
||||
qty_tick_unit = 25;
|
||||
qty_tick_base = 10000;
|
||||
break;
|
||||
case 4000:
|
||||
qty_tick_unit = 25;
|
||||
qty_tick_base = 100000;
|
||||
break;
|
||||
case 20:
|
||||
qty_tick_unit = 5;
|
||||
qty_tick_base = 100;
|
||||
break;
|
||||
case 200:
|
||||
qty_tick_unit = 5;
|
||||
qty_tick_base = 1000;
|
||||
break;
|
||||
case 2000:
|
||||
qty_tick_unit = 5;
|
||||
qty_tick_base = 10000;
|
||||
break;
|
||||
case 20000:
|
||||
qty_tick_unit = 5;
|
||||
qty_tick_base = 100000;
|
||||
break;
|
||||
default:
|
||||
bts_assert_msg(false, "Invalid qty tick");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool is_number(const std::string &s) {
|
||||
return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit);
|
||||
}
|
||||
void GeneralMarketDataListener::_copy_security_common(
|
||||
const bts::domain::Security &from, uint64_t key, FixedSecurity &to) const {
|
||||
to.key = key;
|
||||
to.numeric_id = key;
|
||||
to.numeric_id =is_number(from.security_common.exchange_sec_id)?std::stoul(from.security_common.exchange_sec_id):key;
|
||||
auto symbol = from.security_common.symbol;
|
||||
to.symbol(symbol);
|
||||
to.security_desc(from.security_common.desc);
|
||||
@@ -648,6 +749,24 @@ void GeneralMarketDataListener::_copy_security_common(
|
||||
to.display_factor = 0.0;
|
||||
to.price_break = 0.0;
|
||||
to.low_tick = to.high_tick = from.security_common.min_price_increment;
|
||||
auto &sc = from.security_common;
|
||||
double min_qty = sc.min_quantity;
|
||||
double qty_incr = sc.min_quantity_increment;
|
||||
if (!(min_qty > 0)) {
|
||||
min_qty = qty_incr;
|
||||
} else if (!(qty_incr > 0)) {
|
||||
qty_incr = min_qty;
|
||||
}
|
||||
double max_qty = sc.max_quantity;
|
||||
if (qty_incr > 0 && min_qty > 0 && max_qty > min_qty) {
|
||||
float qty_tick_unit;
|
||||
float qty_tick_base;
|
||||
if (get_qty_info(qty_incr, qty_tick_unit, qty_tick_base)) {
|
||||
to.add_quantity_info(
|
||||
min_qty, max_qty, qty_tick_unit, qty_tick_base,
|
||||
std::numeric_limits<float>::max(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t GeneralMarketDataListener::_generate_security_key(
|
||||
@@ -685,13 +804,16 @@ void GeneralMarketDataListener::_on_book(
|
||||
_possibly_match_with_sim_orders(
|
||||
bts::md::SecurityKey(book.security_id().value()));
|
||||
LOG(md, Diag,
|
||||
"GeneralMarketDataListener::_on_book post id: {} bid: {}/{}/{} ask: "
|
||||
"GeneralMarketDataListener::_on_book sec: {} sec_id: {} handle_id: {} "
|
||||
"bid: "
|
||||
"{}/{}/{} ask: "
|
||||
"{}/{}/{}",
|
||||
handle->id, me->bids.at(0).price(), me->bids.at(0).direct_size(),
|
||||
me->bids.at(0).size(), me->asks.at(0).price(),
|
||||
me->asks.at(0).direct_size(), me->asks.at(0).size());
|
||||
std::string(handle->handle.security().security_desc()),
|
||||
book.security_id(), handle->id, me->bids.at(0).price(),
|
||||
me->bids.at(0).direct_size(), me->bids.at(0).size(),
|
||||
me->asks.at(0).price(), me->asks.at(0).direct_size(),
|
||||
me->asks.at(0).size());
|
||||
handle->handle.update_direct(time.value(), me->bids, me->asks);
|
||||
}
|
||||
|
||||
} // namespace bts::md
|
||||
//#endif
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/md/SecurityIndex.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace bts::md {
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
|
||||
#include "lib/domain/Book.h"
|
||||
#include "lib/domain/SecurityID.h"
|
||||
#include "lib/md/PriceSize.h"
|
||||
#include "lib/utils/TimeStamp.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace bts::domain {
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
#include "lib/utils/ConcurrentPool.h"
|
||||
#include "lib/utils/thread_manager.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
@@ -3,13 +3,9 @@
|
||||
#include "IntPrice.h"
|
||||
#include "MDProducerHandle.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/domain_model/SecurityKey.h"
|
||||
#include "lib/utils/TimeStamp.h"
|
||||
|
||||
#include <btree/map.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#ifndef _WIN32
|
||||
#include "MBOServiceServer.h"
|
||||
|
||||
#include "ExchangeOrderKey.h"
|
||||
#include "MBOProcessor.h"
|
||||
#include "MDClient.h"
|
||||
@@ -8,11 +7,8 @@
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/orderman/orderman_utils/orderman_common.h"
|
||||
#include "lib/utils/Dispatcher.h"
|
||||
#include "lib/utils/TimeStamp.h"
|
||||
|
||||
#include "lib/utils/LockBox.h"
|
||||
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#ifndef _WIN32
|
||||
#include "lib/api/public/ServiceContext.h"
|
||||
#include "lib/api/services/MBOService.server.pb.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace bts {
|
||||
namespace utils {
|
||||
|
||||
@@ -3,16 +3,12 @@
|
||||
#include "MBOProcessor.h"
|
||||
#include "MemorySegmentUtils.h"
|
||||
#include "SecurityCache.h"
|
||||
#include "lib/config/config.h"
|
||||
#include "lib/config/protos/market_data.pb.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/ShmSegmentName.h"
|
||||
#include "lib/md/SymbolLoader.h"
|
||||
#include "lib/utils/SharedList.h"
|
||||
#include "lib/utils/WaitFreeQueue.h"
|
||||
#include "lib/utils/bts_chrono.h"
|
||||
#include "lib/utils/sane_algorithm.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <btree/map.h>
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/utils/bts_export.h"
|
||||
#include "lib/utils/bts_no_copy.h"
|
||||
|
||||
#include "ISyntheticSecurityService.h"
|
||||
#include "MDConsumerHandle.h"
|
||||
#include "SecurityPtr.h"
|
||||
#include "UserSymbol.h"
|
||||
#include "common.h"
|
||||
#include "lib/utils/bts_export.h"
|
||||
|
||||
#include <functional>
|
||||
#include <google/protobuf/repeated_field.h>
|
||||
|
||||
+31
-155
@@ -1,7 +1,11 @@
|
||||
#ifndef NO_CPP0X
|
||||
#include "MDConsumerHandle.h"
|
||||
#include "MBOProcessor.h"
|
||||
#include "lib/domain/BookSide.h"
|
||||
#include "lib/domain/DomainSecurityUtils.h"
|
||||
#include "lib/domain/synthetic_book_side.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/os/os.h"
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
@@ -13,6 +17,10 @@ using small_vector = boost::container::small_vector<T, 4>;
|
||||
namespace bts {
|
||||
namespace md {
|
||||
|
||||
using bts::domain::DomainSecurityUtils;
|
||||
using std::make_tuple;
|
||||
using std::tie;
|
||||
|
||||
MDConsumerHandle::MDConsumerHandle() {
|
||||
os::memset(this, 0, sizeof(MDConsumerHandle));
|
||||
_type = HANDLETYPE_VOID;
|
||||
@@ -102,125 +110,6 @@ TimeStamp copy_depth(
|
||||
/////////////////////////////
|
||||
// SYNTHETIC DEPTH
|
||||
/////////////////////////////
|
||||
struct Level {
|
||||
double price;
|
||||
double size;
|
||||
};
|
||||
struct DepthTraversal {
|
||||
std::array<Level, COCONUT_MAX_DEPTH> levels;
|
||||
size_t head;
|
||||
api::Side side;
|
||||
double qty, price_mult;
|
||||
DepthTraversal(
|
||||
api::Side side,
|
||||
double qty,
|
||||
double price_mult,
|
||||
const PriceSize *depth,
|
||||
size_t depth_count)
|
||||
: levels(), head(0), side(side), qty(qty), price_mult(price_mult) {
|
||||
// Copy prices into mutable storage
|
||||
for (size_t x = 0; x < depth_count; ++x) {
|
||||
levels[x].price = depth[x].price();
|
||||
levels[x].size = depth[x].size();
|
||||
}
|
||||
}
|
||||
double available_size() const {
|
||||
double available = 0.0;
|
||||
auto cur = head;
|
||||
bool partial = false;
|
||||
while (cur < levels.size() && available < 1) {
|
||||
available += levels[cur].size / qty;
|
||||
if (available < 1.0)
|
||||
partial = true;
|
||||
++cur;
|
||||
}
|
||||
if (partial && available >= 1.0)
|
||||
return 1.0;
|
||||
return available;
|
||||
}
|
||||
double consume(double size) {
|
||||
bts_assert_d(size <= available_size());
|
||||
double take = size * qty;
|
||||
double price = 0.0;
|
||||
while (take > 0) {
|
||||
if (levels[head].size >= take) {
|
||||
price += levels[head].price * price_mult * (take / qty);
|
||||
levels[head].size -= take;
|
||||
if (levels[head].size <= 0)
|
||||
++head;
|
||||
break;
|
||||
} else {
|
||||
double available = levels[head].size;
|
||||
price += levels[head].price * price_mult * (available / qty);
|
||||
levels[head].size = 0;
|
||||
++head;
|
||||
take -= available;
|
||||
}
|
||||
}
|
||||
return price / size;
|
||||
}
|
||||
};
|
||||
|
||||
void copy_synthetic_side(
|
||||
size_t count,
|
||||
api::Side side,
|
||||
const api::ContractSpecification &spec,
|
||||
small_vector<DepthTraversal> &leg_travs,
|
||||
Level *out_price) {
|
||||
size_t out_index = 0;
|
||||
bool first = true;
|
||||
while (out_index < count) {
|
||||
|
||||
double price = 0.0;
|
||||
// Find the leg with the minimum available size
|
||||
double size = std::numeric_limits<double>().max();
|
||||
|
||||
for (const auto &dt : leg_travs) {
|
||||
auto sz = dt.available_size();
|
||||
// If we can't get 1.0 from any leg we're done
|
||||
if (sz <= size)
|
||||
size = sz;
|
||||
if (size < 1.0)
|
||||
break;
|
||||
}
|
||||
size = (int64_t)size;
|
||||
|
||||
if (size < 1.0) {
|
||||
// We've run out of book so we nan out any remaining levels and finish
|
||||
size_t start = first ? out_index : out_index + 1;
|
||||
for (size_t x = start; x < count; ++x) {
|
||||
out_price[x].price = os::nan();
|
||||
out_price[x].size = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Take that size from each leg
|
||||
for (auto &dt : leg_travs) {
|
||||
double temp = dt.consume(size);
|
||||
price += temp;
|
||||
}
|
||||
bts_assert_d(!os::is_nan(price));
|
||||
|
||||
// Round to tick size
|
||||
price = securities::checked_round_to_quote_price(spec, price, side);
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
out_price[out_index].price = price;
|
||||
}
|
||||
|
||||
if (price == out_price[out_index].price) {
|
||||
out_price[out_index].size += size;
|
||||
} else {
|
||||
out_index += 1;
|
||||
if (out_index < count) {
|
||||
out_price[out_index].price = price;
|
||||
out_price[out_index].size = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const spark::LegSpreaderParams &get_spreader_params(
|
||||
const SyntheticStrategy &synth, security_key key) {
|
||||
@@ -239,10 +128,11 @@ TimeStamp copy_synthetic_depth(
|
||||
size_t count,
|
||||
PriceSize *bid,
|
||||
PriceSize *ask) {
|
||||
small_vector<DepthTraversal> bid_leg_travs;
|
||||
small_vector<DepthTraversal> ask_leg_travs;
|
||||
bid_leg_travs.reserve(synth.legs.size());
|
||||
ask_leg_travs.reserve(synth.legs.size());
|
||||
domain::SyntheticBookSide synth_bids;
|
||||
domain::SyntheticBookSide synth_asks;
|
||||
// Store bid/ask for each leg (2 BookSides per leg).
|
||||
std::vector<domain::BookSide> depth_cache;
|
||||
depth_cache.resize(2 * synth.legs.size());
|
||||
TimeStamp rv = std::numeric_limits<TimeStamp>::min();
|
||||
for (size_t x = 0; x < synth.legs.size(); ++x) {
|
||||
std::array<PriceSize, COCONUT_MAX_DEPTH> bids, asks;
|
||||
@@ -258,41 +148,27 @@ TimeStamp copy_synthetic_depth(
|
||||
}
|
||||
if (r == HandleReadResult::BUSY || t == TimeStamp::invalid())
|
||||
return TimeStamp::invalid();
|
||||
|
||||
// NET Change
|
||||
if (synth.security.synthetic().price_calc() ==
|
||||
api::SyntheticContract::PRICECALC_NET_CHANGE) {
|
||||
auto settle = synth.legs[x].settlement_price();
|
||||
for (size_t d = 0; d < COCONUT_MAX_DEPTH; ++d) {
|
||||
bids[d] = {bids[d].int_price() - settle, bids[d].size()};
|
||||
asks[d] = {asks[d].int_price() - settle, asks[d].size()};
|
||||
}
|
||||
}
|
||||
|
||||
rv = std::max(rv, t);
|
||||
const auto &leg = synth.security.synthetic().legs(x);
|
||||
const auto &eff_bid = leg.side() == api::SIDE_BUY ? bids : asks;
|
||||
const auto &eff_ask = leg.side() == api::SIDE_BUY ? asks : bids;
|
||||
bid_leg_travs.push_back(DepthTraversal(
|
||||
leg.side(), leg.quantity(), leg.price_multiplier(), &eff_bid[0],
|
||||
eff_bid.size()));
|
||||
ask_leg_travs.push_back(DepthTraversal(
|
||||
leg.side(), leg.quantity(), leg.price_multiplier(), &eff_ask[0],
|
||||
eff_ask.size()));
|
||||
}
|
||||
std::array<Level, COCONUT_MAX_DEPTH> scratch{};
|
||||
const auto &spec = synth.security.contract_spec();
|
||||
copy_synthetic_side(
|
||||
count, api::SIDE_BUY, spec, bid_leg_travs, scratch.data());
|
||||
for (size_t x = 0; x < count; ++x) {
|
||||
bid[x] = PriceSize_from_double<11>(scratch[x].price, scratch[x].size);
|
||||
}
|
||||
memset(scratch.data(), 0, sizeof(scratch));
|
||||
copy_synthetic_side(
|
||||
count, api::SIDE_SELL, spec, ask_leg_travs, scratch.data());
|
||||
for (size_t x = 0; x < count; ++x) {
|
||||
ask[x] = PriceSize_from_double<11>(scratch[x].price, scratch[x].size);
|
||||
PriceSize *eff_bid, *eff_ask;
|
||||
domain::Quantity rel_qty;
|
||||
tie(eff_bid, eff_ask, rel_qty.value()) =
|
||||
leg.side() == api::SIDE_BUY
|
||||
? make_tuple(bids.data(), asks.data(), leg.quantity())
|
||||
: make_tuple(asks.data(), bids.data(), -leg.quantity());
|
||||
// Bid
|
||||
domain::BookSide &cbids = depth_cache.at(x);
|
||||
DomainSecurityUtils::convert(eff_bid, COCONUT_MAX_DEPTH, cbids);
|
||||
synth_bids = synth_bids.add_leg({rel_qty, cbids.data(), cbids.size()});
|
||||
// Ask
|
||||
domain::BookSide &casks = depth_cache.at(x + synth.legs.size());
|
||||
DomainSecurityUtils::convert(eff_ask, COCONUT_MAX_DEPTH, casks);
|
||||
synth_asks = synth_asks.add_leg({rel_qty, casks.data(), casks.size()});
|
||||
}
|
||||
DomainSecurityUtils::convert(
|
||||
synth_bids.data(), synth_bids.size(), bid, count);
|
||||
DomainSecurityUtils::convert(
|
||||
synth_asks.data(), synth_asks.size(), ask, count);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#include "lib/md/Shm/MDStructure.h"
|
||||
#include "lib/sparkproto/SparkProtos.pb.h"
|
||||
#include "lib/utils/bts_export.h"
|
||||
#include "lib/utils/bts_no_copy.h"
|
||||
#include "lib/utils/sane_algorithm.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/utils/SharedList.h"
|
||||
#include "lib/utils/WaitFreeQueue.h"
|
||||
#include <iomanip>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
#include "MDProducerChannel.h"
|
||||
#include "Shm/MDStructure.h"
|
||||
#include "lib/utils/bts_export.h"
|
||||
#include "lib/utils/bts_no_copy.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
|
||||
#include <tbb/concurrent_queue.h>
|
||||
#include <vector>
|
||||
|
||||
namespace bts {
|
||||
namespace co2 {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#ifndef NO_CPP0X
|
||||
#include "MemorySegmentUtils.h"
|
||||
#include "lib/config/config.h"
|
||||
#include "lib/config/protos/market_data.pb.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
@@ -3,17 +3,9 @@
|
||||
#include "IntPrice.h"
|
||||
#include "SizeTypes.h"
|
||||
#include "common.h"
|
||||
|
||||
#include "lib/os/os.h"
|
||||
#include "lib/utils/Optimistic.h"
|
||||
#include "lib/utils/bts_array_size.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/bts_no_copy.h"
|
||||
#include "lib/utils/warn_ctrl.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace bts {
|
||||
|
||||
@@ -5,11 +5,9 @@
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/security_type_index.h"
|
||||
#include "lib/md/venue_index.h"
|
||||
#include "lib/utils/Dispatcher.h"
|
||||
#include "lib/utils/LockBox.h"
|
||||
|
||||
#include <btree/map.h>
|
||||
#include <mutex>
|
||||
#include <tbb/concurrent_queue.h>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
#include "MDConsumerHandle.h"
|
||||
#include "SecurityPtr.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/domain_model/SecurityKey.h"
|
||||
#include "lib/md/ExchangeSecurityKey.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace bts {
|
||||
|
||||
@@ -2,14 +2,9 @@
|
||||
|
||||
#include "SecurityPtr.h"
|
||||
#include "UserSymbol.h"
|
||||
#include "common.h"
|
||||
#include "lib/api/services/SyntheticSecurityService.pb.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/domain_model/SecurityKey.h"
|
||||
#include "lib/domain_model/common.h"
|
||||
#include "lib/md/MDProducerHandle.h"
|
||||
#include "lib/utils/ResultEnum.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
|
||||
#include <functional>
|
||||
#include <set>
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
#ifndef _WIN32
|
||||
#include "SecurityIDRecorder.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/persistence/IStore.h"
|
||||
#include "lib/persistence/SqliteStore.h"
|
||||
#include <boost/pool/object_pool.hpp>
|
||||
|
||||
#include "lib/config/config.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/Sqlite_utils.h"
|
||||
#include "lib/utils/checked_arithmetic.h"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <tbb/concurrent_queue.h>
|
||||
|
||||
#include "lib/config/protos/market_data.pb.h"
|
||||
#include "lib/config/protos/system_config.pb.h"
|
||||
|
||||
#include <boost/pool/object_pool.hpp>
|
||||
#include <memory>
|
||||
#include <sqlite/sqlite3.h>
|
||||
#include <stdexcept>
|
||||
@@ -31,7 +24,6 @@ namespace md {
|
||||
using bts::api::Security;
|
||||
using bts::api::SecurityType;
|
||||
using bts::api::Venue;
|
||||
using bts::config::SystemConfig;
|
||||
using bts::persistence::IStore;
|
||||
using bts::persistence::SqliteStore;
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#pragma once
|
||||
#ifndef _WIN32
|
||||
#include "MDProducerHandle.h"
|
||||
#include "common.h"
|
||||
#include "lib/persistence/IStore.h"
|
||||
#include "lib/utils/Sqlite_utils.h"
|
||||
#include <atomic>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
Loaded 100 of 205 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user