Compare commits

...
Author SHA1 Message Date
Taha 021431b567 Merging 2025-03-03 23:21:30 -05:00
Mohammad Arham e7b426cd3d Fix: Exchange sec id fixed 2025-03-03 17:50:25 +00:00
arham ee02c3ffa7 Add: small changes 2025-03-03 12:03:13 -05:00
Taha 21b725e5df LocalMDDaemon works now 2025-03-03 00:07:31 -05:00
Taha 649931ae3c Trying to make the local md daemon work 2025-03-02 02:23:17 -05:00
Taha 7b5f034ed2 Local/Direct bbo subscription 2025-03-02 02:23:17 -05:00
Mohammad Arham 74b376ce56 Add: run only securities nnot books databento 2025-02-28 15:01:53 +00:00
Mohammad Arham fc96b9e436 Add: TBBO to databento 2025-02-28 14:28:36 +00:00
Mohammad Arham fd015577b2 MD working on my laptop 2025-02-26 02:49:41 +00:00
Mohammad Arham 8d142b8a74 Fix: forgot to push a file 2025-02-19 03:36:14 +00:00
Mohammad Arham 8210b9f35f Fix: change onix exe so it uses dispatch fix msgs 2025-02-19 03:33:00 +00:00
Mohammad Arham 12b20b05ce Fix: OKX executions sends er only when dispatch called 2025-02-18 16:02:10 +00:00
Mohammad Arham 72efbccdbd Fix: USDC swaps md working for okx 2025-02-14 23:02:49 +00:00
Mohammad Arham 33643e9eab Fix: make log resize work again 2025-02-14 22:59:35 +00:00
Mohammad Arham 0f6dc6f40b Fix: Typo in Okx execution 2025-02-13 23:38:27 +00:00
Mohammad Arham 8c778f5e41 Fix: Added tdmode to config for okx execution 2025-02-13 23:32:53 +00:00
Mohammad Arham f308654200 Fix: okx execution fix large spreader orderd 2025-02-13 23:27:32 +00:00
Mohammad Arham bdf5b980fc Add: config for sim trading 2025-02-13 22:06:23 +00:00
Mohammad Arham 914ac597cd Vesrsion: Update to v2.2.5 deploying to jame prod 2025-02-13 14:40:27 +00:00
Mohammad Arham 4aa6a77680 Fix: OKX execution works as long as spreader not to big 2025-02-13 06:09:43 +00:00
Mohammad Arham 6b59dd84ee Fix: Ordermanager will not try to remake orders from er after restart 2025-02-13 06:09:01 +00:00
Mohammad Arham 4e2c5f2d17 Fix: Okx USDT perps fix re added 2025-02-13 06:08:27 +00:00
Mohammad Arham 9a1b94a1b0 Add: fill id to okx execution report 2025-02-13 01:13:50 +00:00
Mohammad Arham 13b3c55771 Change: lst msg only entered if error 2025-02-12 23:13:59 +00:00
Mohammad Arham d0a6fcdb9c Add: Stricter Assert if websocket goes down okx execution 2025-02-12 22:23:53 +00:00
John Colvin 1eb13028c1 ui: Get qty display settings from md instead of config 2025-02-12 15:59:51 -05:00
John Colvin 424e5ff16b Get Spark version at runtime via reflection
So it doesn't need to be hardcoded per release
2025-02-12 15:59:51 -05:00
John Colvin bed389885b Support qty tick size 10e-9
Some spots at OKX need this (e.g., BTC-AED)
2025-02-12 20:42:28 +00:00
John Colvin 85cf25a57a fix: Get min qty and min increment from correct fields in okx json
"lotSz" represents min qty, and "minSz" represents min qty increment. We
had them backwards before.
2025-02-12 20:36:40 +00:00
John Colvin 61471d4675 diag: Show qty info fields in FixedSecurity json 2025-02-12 20:34:48 +00:00
John Colvin d83435d49d Move FixedSecurity to_json fn to cpp
Doesn't make sense to inline; it's too big.
2025-02-12 20:34:47 +00:00
John Colvin 582fc10dce Add get_binance_md.sh for quick/easy troubleshooting 2025-02-12 20:34:47 +00:00
John Colvin 5969985d24 cleanup: Remove unnecessary includes 2025-02-12 20:34:47 +00:00
John Colvin fabe7d665c devcontainer: Install clang-format extension 2025-02-12 20:34:47 +00:00
John Colvin 7ce75bb8e7 devcontainer: Increase ram and shared mem
In order to support large symbol sets for Alphaworx
2025-02-12 20:34:47 +00:00
Mohammad Arham b8e1c3cd60 Add: Fix build Issue 2025-02-12 19:48:52 +00:00
Taha 4909944694 Fixing PNL for perps (its base don mid market now) 2025-02-12 11:20:55 -05:00
Mohammad Arham 7258af09b4 Add: Basic Okx execution working 2025-02-12 11:55:50 +00:00
Mohammad Arham 7f2ce6c1a9 Add: Okx execution cfg 2025-02-12 11:06:45 +00:00
Mohammad Arham 4835725e1c Add: diag logging to gttp client 2025-02-12 11:06:09 +00:00
Mohammad Arham 6a9e8de475 Deployment: Updating version numbers 2025-02-11 05:06:21 +00:00
Taha Afzal 4639951acc Fixing Sprader's reportin of its filled qty 2025-02-11 03:04:30 +00:00
Taha d8de211aac Front end does NOT filter nay CBOE or CME expirations 2025-02-10 17:49:14 -05:00
John Colvin 87394bc60b ui: Increase Currency col width so filter icon isn't clipped 2025-02-10 15:31:31 -05:00
John Colvin beba30f65c fix (ui): Remove temp hack that assumed synth qty always notional 2025-02-10 15:31:31 -05:00
John Colvin 2d58424d8f ui (mview): Hook up currency filter in vm
Make it a discrete values filter (listbox) while we're at it. This seems
more practical than regex.
2025-02-10 15:31:31 -05:00
John Colvin d1bbfa3791 Revert batch subscribe change in Binance 2025-02-10 19:59:42 +00:00
John Colvin aab17f460f cleanup: Remove docker submodules again
They just keep coming back.
2025-02-10 19:59:32 +00:00
John Colvin 461ef53322 devcontainer: Clean up and simplify
* Remove mounted dirs (the repo is automatically persisted in a volume)
* Remove user specific shell env initialization scripts (they're more
  trouble than they're worth, often break the devcontainer build)
* Remove redundant settings already defined in settings.json
2025-02-10 16:55:53 +00:00
Taha Afzal 2486bc90b2 Spreader is hooked to the priceCal dropdown while creating the spreader 2025-02-10 03:10:11 +00:00
John Colvin 1c5cbaf60b Change: Update version to 2.2.4-1 2025-02-08 13:19:41 -05:00
John Colvin b8de447cfb fix (pnl ui): Assume rows w/ exp DateTime.MinValue are Spots
Not the most elegant fix, but it'll do for now.

Bugs (fixed):
  * The Pnl window groups perpetual futures with spots
2025-02-08 13:18:27 -05:00
Mohammad Arham 8bfc2d40f9 Update: V2.2.4 pnl working now 2025-02-08 12:43:13 -05:00
Taha c09fd71c94 Fixing PNL for spots 2025-02-08 06:55:07 -05:00
Mohammad Arham f3b037553e Add: Batch subs in binance sub 2025-02-08 06:44:10 +00:00
Taha Afzal d386d93166 SpreaderChildOrder::on_order_udpate, changing an asserrt to an error lgo to monitor condition 2025-02-07 15:06:16 +00:00
Mohammad Arham 5217742c71 Add: SendingTime tag to cboe execution 2025-02-07 12:26:11 +00:00
Taha c59ec95460 Sheets and spreadbox theos can now optionally be set to market mid 2025-02-06 17:42:12 -05:00
Mohammad Arham 70f01583ec ADD: CBOE TIME and SALES working 2025-02-06 20:42:58 +00:00
Mohammad Arham 66d2f9eb63 Add: Quote algo updates, theos removed 2025-02-06 03:08:46 +00:00
John Colvin 4625bed8b8 Change: Update version to v2.2.3-1 2025-02-05 17:01:11 -05:00
John Colvin f18fb332b5 fix (orderbook): Make ColumnKeys enum 64bit
We rely on bit flags to indicate "dirty" columns in the vm. Need to do
this now that we have > 31 columns.
2025-02-05 17:01:11 -05:00
John Colvin 8746a6f5ca fix (orderbook): Refresh Notional Value view when it changes in vm 2025-02-05 16:30:13 -05:00
John Colvin 702d47abb5 fix (orderbook): Fix typos in default settings for Notional Value 2025-02-05 16:29:29 -05:00
John Colvin bf2a54717c ui (orderbook): Add Filled Notional Value column 2025-02-05 16:13:34 -05:00
John Colvin 9db779fb25 fix (hack): Assume synth quantities are always notional values 2025-02-05 16:07:07 -05:00
Mohammad Arham f55ddec708 Add: Vesrion logs to bothserver and client 2025-02-05 09:50:33 -05:00
Mohammad Arham 6e942dd2d4 Change: Updated version to v2.2.3 2025-02-05 08:38:31 -05:00
Mohammad Arham 142310a17a Add: SPXW to default for cboe 2025-02-05 12:16:47 +00:00
John Colvin 7f99a89a95 ui (fills): Add notional value column 2025-02-05 00:00:41 -05:00
John Colvin bfce663b4b fix (orderbook): Hook up notional val filter in vm, persist settings 2025-02-04 23:42:58 -05:00
John Colvin 33c743643d fix (orderbook): Include fractional qty in notional val 2025-02-04 23:34:47 -05:00
Taha Afzal 858e0a389b Fixed the issue where a notioanal quoter would send a 0 qty order 2025-02-05 04:16:53 +00:00
Taha Afzal 9c3bb85674 Notional qty handling in spreader 2025-02-05 04:16:53 +00:00
Taha Afzal f4930e8cd4 Added notional to PriceCalc type for the sprader, added code to compute notional books in SpreaderUtils 2025-02-05 04:16:53 +00:00
John Colvin f21e7f3602 ui (orderbook): Show synth notional value in terms of first leg 2025-02-04 16:54:37 -05:00
John Colvin b56c35aca4 ui (ladder): Show synth notional value in terms of first leg 2025-02-04 16:54:37 -05:00
John Colvin e6ac83196e fix (benign): Actually dispose _vm_sub in LadderGridHost 2025-02-04 16:54:37 -05:00
John Colvin 7b6f74fc4a cleanup: Get rid of 'mimic tt' ladder config
This config was leftover from custom DJ work. We no longer need it.
2025-02-04 16:54:37 -05:00
John Colvin f754953fac cleanup: Remove duplicate equality check 2025-02-04 16:54:37 -05:00
Taha 2dd1143d9f Fixed spx spreadbox theos 2025-02-04 16:31:17 -05:00
Mohammad Arham e1ee3275d8 Add: Filter secs upto n months databento 2025-01-31 21:46:11 +00:00
John Colvin 2549e406f3 fix: Compute notional value correctly when ticks per row > 1
It's the sum of price * qty for each level in the row. Previously, we
were multiplying the total qty by the same price (smallest in the row).
2025-01-29 16:28:28 -05:00
John Colvin d5024f1858 fix build: Don't check if unsigned int < 0 2025-01-28 20:20:06 +00:00
John Colvin 3623ac96db fix: Multiply array size by sizeof(T) 2025-01-28 20:19:51 +00:00
John Colvin 19834625f9 fix: Don't fall through switch case 2025-01-28 19:59:32 +00:00
John Colvin c8fc649b6b fix build: Pass config arg to cmake --build
For Ninja Multi-Config builds (i.e., api server and data streams)
2025-01-28 19:59:09 +00:00
John Colvin 6b1fc1c413 cleanup: Remove unused code 2025-01-28 19:59:09 +00:00
John Colvin 6f086532ac cleanup: Convert co2::TradeMessage in DomainSecurityUtils
Instead of domain::TradeRecord constructor. The domain types should have
minimal dependency on protos.
2025-01-28 19:59:09 +00:00
John Colvin dc66282225 cleanup: Rename UINT32 -> VALUE in qty macro names
Should have done this when we changed qty primitive from uint32 to float
2025-01-28 19:59:09 +00:00
John Colvin e2c74c0bb3 cleanup: Don't redefine CONTAINS macro 2025-01-28 19:59:09 +00:00
John Colvin e89d50eec1 cleanup: Move large fns from header to cpp 2025-01-28 19:59:08 +00:00
John Colvin 8bebbc278d cleanup: Get rid of 'using namespaces'
In headers, this is an objectively bad practice, since the type aliases
leak into dependent code and can cause unexpected naming conflicts. In
source files, it is debatable, but I prefer explicit aliases of the form
'using Foo = Bar'. The intentions are clear, and the resulting code is
naturally resistent to naming conflicts.
2025-01-28 19:59:08 +00:00
John Colvin 2e6d12c9c1 cleanup: Remove unnecessary includes 2025-01-28 19:59:08 +00:00
John Colvin 102a5e23ea vscode: Add Tests Sparkd entries to tasks.json 2025-01-28 19:59:08 +00:00
John Colvin 582dc791db vscode: Add Run Sparkd (Skip Build) entry to tasks.json 2025-01-28 19:59:08 +00:00
John Colvin afb35d8c25 Compute synth book qty in terms of first leg 2025-01-28 19:59:08 +00:00
John Colvin 3a32ed5fe6 Fix BookSide equality operators 2025-01-27 16:35:22 +00:00
John Colvin 4e01697327 Add equality and output operators to BookLevel 2025-01-27 16:25:32 +00:00
John Colvin 22032c1742 Add fmt::formatter for domain::Side
So we can pass it to LOG macros, etc.
2025-01-27 16:25:15 +00:00
John Colvin 0a9bce317c Add contract qty info to security proto (and FixedSecurity)
We should get this kind of security metadata from the exchange rather
than config.
2025-01-25 20:41:47 +00:00
Mohammad Arham 40e44db597 Fix: Added GCHandle in attempt to fix ui crash 2025-01-22 02:15:55 -05:00
Mohammad Arham 4a447e44e1 Add: Version to first log 2025-01-22 02:14:19 -05:00
205 changed files with 6739 additions and 3547 deletions

No files matched your search

+6 -18
View File
@@ -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"
]
}
}
}
}
+62
View File
@@ -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",
},
]
}
+47
View File
@@ -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) {
+11
View File
@@ -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
+243
View File
@@ -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}");
}
}
}
}
}
+104
View File
@@ -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();
}
}
+23 -8
View File
@@ -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);
}
}
+91 -58
View File
@@ -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;
+17 -6
View File
@@ -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
+1 -1
View File
@@ -100,7 +100,7 @@
}
void PerformMEFComposition(string config_source) {
Log.Ui.info($"Generating MEF dependencies for the shell " +
Log.Ui.info($"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(
+28 -3
View File
@@ -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) {
+136 -4
View File
@@ -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();
}
}
}
}
}
+3 -2
View File
@@ -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;
}
+201 -174
View File
@@ -4,16 +4,11 @@
#include "lib/api/services/OrderService.server.pb.h"
#include "lib/config/config.h"
#include "lib/config/protos/toggles.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/utils/PBUtils.h"
#include <memory>
namespace bts {
namespace admin {
using namespace bts::api::admin;
using namespace bts::api::orderman;
class AdminServiceUtils {
public:
struct ExchangeSubAccountResolver {
@@ -33,7 +28,7 @@ class AdminServiceUtils {
return is_int_account ? entry.bts_account_name()
: entry.exchange_subaccount();
};
if(sec == nullptr)
if (sec == nullptr)
return nullptr;
for (const auto &u : exch_sub_accounts.accounts()) {
if (get_account(u) == account)
@@ -59,224 +54,247 @@ class AdminServiceUtils {
////////////////////////////////////////////
// finds the max id out of all the existing connections
// (todo: deleted connections)
static uint32_t find_max_connection_id(const AdminData &data);
static uint32_t find_max_connection_id(const api::admin::AdminData &data);
// finds a connection by name and returns a mutable pointer to it
static const ExchangeConnection *find_connection(
const AdminData &data, const std::string &connection_name);
static const api::admin::ExchangeConnection *find_connection(
const api::admin::AdminData &data, const std::string &connection_name);
// finds the presence of an exchange in a connection
static bool has_exchange(
const ExchangeConnection &conn, bts::api::Venue exchange);
const api::admin::ExchangeConnection &conn, api::Venue exchange);
// finds the default connections for an exchange
// in a given account
static const ExchangeConnection *find_default_account_connection(
const AdminData &data,
const BTSAccount &account,
bts::api::Venue exchange);
static const ExchangeConnection *resolve_cme_connection(
const AdminData &data,
const BTSAccount &account,
static const api::admin::ExchangeConnection *find_default_account_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
api::Venue exchange);
static const api::admin::ExchangeConnection *resolve_cme_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
static const ExchangeConnection *resolve_ice_connection(
const AdminData &data,
const BTSAccount &account,
static const api::admin::ExchangeConnection *resolve_ice_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
static const ExchangeConnection *resolve_sim_connection(
const AdminData &data,
const BTSAccount &account,
static const api::admin::ExchangeConnection *resolve_sim_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
static const ExchangeConnection *resolve_connection(
const AdminData &data,
const BTSAccount &account,
const bts::api::Security &sec);
static const api::admin::ExchangeConnection *resolve_connection(
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
const api::Security &sec);
////////////////////////////////////////////
// connection mutators /////////////////////
////////////////////////////////////////////
// adds a new connection (does not check for name uniqueness)
static ExchangeConnection *add_connection(
AdminData &data, const ExchangeConnection &new_connection);
static api::admin::ExchangeConnection *add_connection(
api::admin::AdminData &data,
const api::admin::ExchangeConnection &new_connection);
// reads the connections from config and fills in the
// admin data (with names only)
static void fill_connections_from_config(AdminData &admin_data);
static void fill_connections_from_config(api::admin::AdminData &admin_data);
////////////////////////////////////////////
// account accessors ///////////////////////
////////////////////////////////////////////
// finds the account from the name and returns
// a mutable pointer to the account
static BTSAccount *find_account(
AdminData &data,
static api::admin::BTSAccount *find_account(
api::admin::AdminData &data,
const std::string &acct_name,
bool include_deleted = false);
static std::map<std::string, uint32_t> get_clearing_account_map(
const AdminData &);
const api::admin::AdminData &);
// finds the account from a clearing account (and its venue)
static BTSAccount const *find_account(
const AdminData &data,
static api::admin::BTSAccount const *find_account(
const api::admin::AdminData &data,
const std::string &clearing_account,
bts::api::Venue);
api::Venue);
// same as above for const pointer
static const BTSAccount *find_account(
const AdminData &data,
static const api::admin::BTSAccount *find_account(
const api::admin::AdminData &data,
const std::string &acct_name,
bool include_deleted = false);
// finds the account from the id and returns
// a mutable pointer to the account
static BTSAccount *find_account(
AdminData &data, uint32_t id, bool include_deleted = false);
static api::admin::BTSAccount *find_account(
api::admin::AdminData &data, uint32_t id, bool include_deleted = false);
// like above but returns a const pointer
static const BTSAccount *find_account(
const AdminData &data, uint32_t id, bool include_deleted = false);
static const api::admin::BTSAccount *find_account(
const api::admin::AdminData &data,
uint32_t id,
bool include_deleted = false);
// this function is an indirection layer for account access
// so deletions are dealt in a more deliberate manner
static std::vector<const BTSAccount *> accounts(
const AdminData &data, bool include_deleted);
static std::vector<BTSAccount *> accounts_mutable(
AdminData &data, bool include_deleted);
static std::vector<const api::admin::BTSAccount *> accounts(
const api::admin::AdminData &data, bool include_deleted);
static std::vector<api::admin::BTSAccount *> accounts_mutable(
api::admin::AdminData &data, bool include_deleted);
// finds the max id amongts all the existing accounts
static uint32_t find_max_acct_id(const AdminData &data);
static uint32_t find_max_acct_id(const api::admin::AdminData &data);
// gets the account data for cme (and goes up the hierarchy
// if needed)
static const CMEAccountData *get_account_cme_data(
const BTSAccount &account, const AdminData &data);
static const ICEAccountData *get_account_ice_data(
const BTSAccount &account, const AdminData &data);
static const SIMAccountData *get_account_sim_data(
const BTSAccount &account, const AdminData &data);
static const api::admin::CMEAccountData *get_account_cme_data(
const api::admin::BTSAccount &account,
const api::admin::AdminData &data);
static const api::admin::ICEAccountData *get_account_ice_data(
const api::admin::BTSAccount &account,
const api::admin::AdminData &data);
static const api::admin::SIMAccountData *get_account_sim_data(
const api::admin::BTSAccount &account,
const api::admin::AdminData &data);
// returns true if an account has a connection
bool static account_has_connection(
const BTSAccount &account, const std::string &connection_name);
const api::admin::BTSAccount &account,
const std::string &connection_name);
// finds the external/clearing account associated
// with an account
static std::string find_account_exchange_account(
const AdminData &data,
const BTSAccount &account,
const api::admin::AdminData &data,
const api::admin::BTSAccount &account,
api::Venue exchange,
AdminServiceOperationResult &out_result);
api::admin::AdminServiceOperationResult &out_result);
// returns a map of the accounts vs their 'is_active'
static std::map<uint32_t, bool> accounts_map(const AdminData &admin_data);
static std::map<uint32_t, bool> accounts_map(
const api::admin::AdminData &admin_data);
// validates that the update to the account is valid
static void validate_account_update(
const AdminData &admin_data,
const BTSAccount &updated_account,
AdminServiceOperationResult &result);
const api::admin::AdminData &admin_data,
const api::admin::BTSAccount &updated_account,
api::admin::AdminServiceOperationResult &result);
// validates that the account exchange changes are valid
// (ex > 12 characters for accounts at CME)
static void validate_account_exchange_data(
const BTSAccount &account, AdminServiceOperationResult &out_result);
static RoutingRecord get_routing_record(
const AdminData &data,
const api::admin::BTSAccount &account,
api::admin::AdminServiceOperationResult &out_result);
static api::orderman::RoutingRecord get_routing_record(
const api::admin::AdminData &data,
const std::string &account_name,
const std::string &user_name,
const bts::api::Security &sec,
AdminServiceOperationResult &out_result);
static RoutingRecord get_routing_record(
const AdminData &data,
const api::Security &sec,
api::admin::AdminServiceOperationResult &out_result);
static api::orderman::RoutingRecord get_routing_record(
const api::admin::AdminData &data,
uint32_t account_id,
uint32_t user_id,
const bts::api::Security &sec,
AdminServiceOperationResult &out_result);
const api::Security &sec,
api::admin::AdminServiceOperationResult &out_result);
////////////////////////////////////////////
// account mutators ////////////////////////
////////////////////////////////////////////
static BTSAccount *create_account(
AdminData &data,
static api::admin::BTSAccount *create_account(
api::admin::AdminData &data,
uint32_t parent_account_id,
const std::string &account_name,
const std::string &description,
bool is_active,
AdminServiceCreateAccountResult &result);
api::admin::AdminServiceCreateAccountResult &result);
// writes over the account data completely
static void update_account(
AdminData &data,
const BTSAccount &updated_account,
AdminServiceOperationResult &result);
api::admin::AdminData &data,
const api::admin::BTSAccount &updated_account,
api::admin::AdminServiceOperationResult &result);
static void delete_account(
AdminData &admin_data,
const IDAPIArgument &id,
AdminServiceOperationResult &result);
api::admin::AdminData &admin_data,
const api::admin::IDAPIArgument &id,
api::admin::AdminServiceOperationResult &result);
static void set_account_exchange_data(
BTSAccount *account, const ExchangeAccountData &exchange_data);
api::admin::BTSAccount *account,
const api::admin::ExchangeAccountData &exchange_data);
// this is not a generally useful function but since
// connection are in config, this cleans up the account's
// connections and syncs it with the list in AdminData
// connections and syncs it with the list in api::admin::AdminData
static void sync_account_connections(
const AdminData &data, BTSAccount &account);
const api::admin::AdminData &data, api::admin::BTSAccount &account);
////////////////////////////////////////////
// group accessors /////////////////////////
////////////////////////////////////////////
// given an id, finds the mutable pointer to a group
static BTSUserGroup *find_group(AdminData &data, uint32_t id);
static api::admin::BTSUserGroup *find_group(
api::admin::AdminData &data, uint32_t id);
// given an id, finds the const pointer to a group
static const BTSUserGroup *const find_group(
const AdminData &data, uint32_t id);
static const api::admin::BTSUserGroup *const find_group(
const api::admin::AdminData &data, uint32_t id);
// given a name, finds the mutable pointer to a group
static BTSUserGroup *find_group(
AdminData &data, const std::string &group_name);
static api::admin::BTSUserGroup *find_group(
api::admin::AdminData &data, const std::string &group_name);
// finds the max id out of all the groups
static uint32_t find_max_group_id(const AdminData &data);
static uint32_t find_max_group_id(const api::admin::AdminData &data);
// travels up the group tree until it finds a populated cme data
static const CMEUserData *get_group_cme_data(
const BTSUserGroup &group, const AdminData &data);
static const ICEUserData *get_group_ice_data(
const BTSUserGroup &group, const AdminData &data);
static const SIMUserData *get_group_sim_data(
const BTSUserGroup &group, const AdminData &data);
static const UserAccountPermissions *const get_group_user_permissions(
const bts::api::admin::AdminData &admin_data, uint32_t group_id);
static const api::admin::CMEUserData *get_group_cme_data(
const api::admin::BTSUserGroup &group,
const api::admin::AdminData &data);
static const api::admin::ICEUserData *get_group_ice_data(
const api::admin::BTSUserGroup &group,
const api::admin::AdminData &data);
static const api::admin::SIMUserData *get_group_sim_data(
const api::admin::BTSUserGroup &group,
const api::admin::AdminData &data);
static const api::admin::UserAccountPermissions *const
get_group_user_permissions(
const api::admin::AdminData &admin_data, uint32_t group_id);
////////////////////////////////////////////
// group mutators/ /////////////////////////
////////////////////////////////////////////
static BTSUserGroup *create_group(
AdminData &data,
static api::admin::BTSUserGroup *create_group(
api::admin::AdminData &data,
uint32_t parent_group_id,
const std::string &group_name,
AdminServiceOperationResult &result);
api::admin::AdminServiceOperationResult &result);
////////////////////////////////////////////
// user accessors //////////////////////////
////////////////////////////////////////////
static BTSUser *find_user(
AdminData &data,
static api::admin::BTSUser *find_user(
api::admin::AdminData &data,
const std::string &user_name,
bool include_deleted = false);
static const BTSUser *find_user(
const AdminData &data,
static const api::admin::BTSUser *find_user(
const api::admin::AdminData &data,
const std::string &user_name,
bool include_deleted = false);
static BTSUser *find_user(
AdminData &data, uint32_t id, bool include_deleted = false);
static const BTSUser *const find_user(
const AdminData &data, uint32_t id, bool include_deleted = false);
static std::vector<const BTSUser *> users(
const AdminData &data, bool include_deleted);
static std::vector<BTSUser *> users_mutable(
AdminData &data, bool include_deleted);
static uint32_t find_max_user_id(const AdminData &data);
static const CMEUserData *get_user_cme_data(
const BTSUser &user, const AdminData &data);
static const ICEUserData *get_user_ice_data(
const BTSUser &user, const AdminData &data);
static const SIMUserData *get_user_sim_data(
const BTSUser &user, const AdminData &data);
static api::admin::BTSUser *find_user(
api::admin::AdminData &data, uint32_t id, bool include_deleted = false);
static const api::admin::BTSUser *const find_user(
const api::admin::AdminData &data,
uint32_t id,
bool include_deleted = false);
static std::vector<const api::admin::BTSUser *> users(
const api::admin::AdminData &data, bool include_deleted);
static std::vector<api::admin::BTSUser *> users_mutable(
api::admin::AdminData &data, bool include_deleted);
static uint32_t find_max_user_id(const api::admin::AdminData &data);
static const api::admin::CMEUserData *get_user_cme_data(
const api::admin::BTSUser &user, const api::admin::AdminData &data);
static const api::admin::ICEUserData *get_user_ice_data(
const api::admin::BTSUser &user, const api::admin::AdminData &data);
static const api::admin::SIMUserData *get_user_sim_data(
const api::admin::BTSUser &user, const api::admin::AdminData &data);
// finds the "exchange user" (ex: "operator_id" at CME or "authorized_trader"
// at ICE) for a user
static std::string find_user_exchange_id(
const AdminData &data,
const BTSUser &user,
const api::admin::AdminData &data,
const api::admin::BTSUser &user,
api::Venue exchange,
AdminServiceOperationResult &out_result);
api::admin::AdminServiceOperationResult &out_result);
// i. uses the exchange and exchange_user to reverse lookup the user(s)
// ii. uses the exchange, connection and exchange_account to reverse lookup
// the account(s) iii. finds the first account from ii that a user from i is
// authorized to trade
static std::string find_user_company_id(
const AdminData &data,
const BTSUser &user,
const api::admin::AdminData &data,
const api::admin::BTSUser &user,
api::Venue exchange,
AdminServiceOperationResult &out_result);
api::admin::AdminServiceOperationResult &out_result);
static std::pair<const BTSAccount *, const BTSUser *>
static std::pair<const api::admin::BTSAccount *, const api::admin::BTSUser *>
find_user_account_matches(
const AdminData &admin_data,
const api::admin::AdminData &admin_data,
const std::string &connection,
const std::string &exchange_account,
const std::string &exchange_user,
@@ -284,100 +302,108 @@ class AdminServiceUtils {
// for a given user finds the account permissions (if permissions are
// derived from the group, it traverses the group hierarcy to find the
// correct permissions.
static const UserAccountPermissions *get_user_permissions(
const AdminData &admin_data, uint32_t user_id);
static const api::admin::UserAccountPermissions *get_user_permissions(
const api::admin::AdminData &admin_data, uint32_t user_id);
// for a given user, fills in the associated_accounts ref arg
// with the accounts the user has permission to trade in
static void find_user_trading_accounts(
const AdminData &admin_data,
const api::admin::AdminData &admin_data,
uint32_t user_id,
std::vector<const BTSAccount *> &associated_accounts);
std::vector<const api::admin::BTSAccount *> &associated_accounts);
// for a given account, fills in the associated_users ref arg
// with the users that can trade into the account
static void find_users_for_trading_account(
const AdminData &admin_data,
const api::admin::AdminData &admin_data,
uint32_t account_id,
std::vector<const BTSUser *> &associated_users);
std::vector<const api::admin::BTSUser *> &associated_users);
// validates that changes in user data satisfy all the
// constraints
static void validate_user_update(
const AdminData &admin_data,
const BTSUser &updated_user,
AdminServiceOperationResult &result);
const api::admin::AdminData &admin_data,
const api::admin::BTSUser &updated_user,
api::admin::AdminServiceOperationResult &result);
// vaidates the exchange part of user data updates
static void validate_user_exchange_data(
const BTSUser &updated_user, AdminServiceOperationResult &out_result);
const api::admin::BTSUser &updated_user,
api::admin::AdminServiceOperationResult &out_result);
static void get_md_entitlement_report(
const AdminData &admin_data,
std::vector<MDEntitlementReportUnit> &entitlements);
const api::admin::AdminData &admin_data,
std::vector<api::admin::MDEntitlementReportUnit> &entitlements);
// Gets a reporting unit out of CMEMicData
static MDEntitlementReportUnit get_cme_md_entitlement_unit(
const BTSUser &user, const CMEMicData &cme_mic_data);
static MDEntitlementReportUnit get_ice_md_entitlement_unit(
const BTSUser &user, const ICEMicData &ice_mic_data);
static api::admin::MDEntitlementReportUnit get_cme_md_entitlement_unit(
const api::admin::BTSUser &user,
const api::admin::CMEMicData &cme_mic_data);
static api::admin::MDEntitlementReportUnit get_ice_md_entitlement_unit(
const api::admin::BTSUser &user,
const api::admin::ICEMicData &ice_mic_data);
static void get_user_symbol_permissions(
const AdminData &data,
const BTSUser &user,
std::vector<UserSymbolMDPermissionUnit> &res);
const api::admin::AdminData &data,
const api::admin::BTSUser &user,
std::vector<api::admin::UserSymbolMDPermissionUnit> &res);
////////////////////////////////////////////
// user mutators ///////////////////////////
////////////////////////////////////////////
static BTSUser *create_user(
AdminData &data,
static api::admin::BTSUser *create_user(
api::admin::AdminData &data,
uint32_t parent_group_id,
const std::string &name,
const std::string &password,
const std::string &full_name,
const std::string &company,
bool is_active,
const LicenseData &license_data,
const LicenseEntitlements &license_entitlements,
AdminServiceCreateEntityResult &result);
static BTSUser *update_user(
AdminData &admin_data,
const BTSUser &updated_user,
AdminServiceOperationResult &result);
const api::admin::LicenseData &license_data,
const api::admin::LicenseEntitlements &license_entitlements,
api::admin::AdminServiceCreateEntityResult &result);
static api::admin::BTSUser *update_user(
api::admin::AdminData &admin_data,
const api::admin::BTSUser &updated_user,
api::admin::AdminServiceOperationResult &result);
static void delete_user(
AdminData &admin_data,
const IDAPIArgument &id,
AdminServiceOperationResult &result);
api::admin::AdminData &admin_data,
const api::admin::IDAPIArgument &id,
api::admin::AdminServiceOperationResult &result);
static void set_user_exchange_data(
BTSUser *user, const ExchangeUserData &exchange_user_data);
static void clear_user_accounts(BTSUser *user);
api::admin::BTSUser *user,
const api::admin::ExchangeUserData &exchange_user_data);
static void clear_user_accounts(api::admin::BTSUser *user);
static void set_user_accounts(
BTSUser *user, const UserAccountPermissions &user_account_permissions);
api::admin::BTSUser *user,
const api::admin::UserAccountPermissions &user_account_permissions);
////////////////////////////////////////////
// risk limits //////////////////////////////
////////////////////////////////////////////
// sorting function used by sort_limits to
// sort SecurityScope's
static bool _sort_security_scopes(
const SecurityScope &a, const SecurityScope &b);
const api::admin::SecurityScope &a, const api::admin::SecurityScope &b);
// sorting function used by sort_limits to
// sort ProductScope's
static bool _sort_product_scopes(
const ProductScope &a, const ProductScope &);
const api::admin::ProductScope &a, const api::admin::ProductScope &);
// this sorts the rules such that the most
// specific ones are at the top. this makes it
// easier to match rules with securities/products
static void sort_limits(BTSAccount &account);
// creates a copy of AdminData with sorted rules
static AdminData sort_limits(const AdminData &admin_data);
static void sort_limits(api::admin::BTSAccount &account);
// creates a copy of api::admin::AdminData with sorted rules
static api::admin::AdminData sort_limits(
const api::admin::AdminData &admin_data);
// find the clip size for a security, account
static const uint32_t find_account_security_clip_size(
const BTSAccount &account, const api::Security &security);
const api::admin::BTSAccount &account, const api::Security &security);
// find the position limit for an account, security
static const AccountSecurityPositionLimitUnit *
static const api::admin::AccountSecurityPositionLimitUnit *
find_account_security_position_limit(
const BTSAccount &account, const api::Security &security);
const api::admin::BTSAccount &account, const api::Security &security);
// find the position limit for an account, product
static const AccountProductPositionLimitUnit *
static const api::admin::AccountProductPositionLimitUnit *
find_account_product_position_limit(
const BTSAccount &account, const api::Product &product);
const api::admin::BTSAccount &account, const api::Product &product);
static bool match_security_scope(
const SecurityScope &scope, const api::Security &security);
const api::admin::SecurityScope &scope, const api::Security &security);
static bool match_product_scope(
const ProductScope &scope, const api::Product &product);
const api::admin::ProductScope &scope, const api::Product &product);
static bool match_product(
const api::Security &security, const api::Product &product);
static bool _match_limit_rule_symbol(
@@ -388,13 +414,14 @@ class AdminServiceUtils {
////////////////////////////////////////////
static bool has_id(uint32_t id);
static bool is_symbol_set(const std::string &symbol);
static void bootstrap_data(bts::api::admin::AdminData &data);
static void bootstrap_data(api::admin::AdminData &data);
static void get_spread_prod_qty(
const api::Security &s,
std::map<std::string, double> &out_map,
double mult = 1.0);
// returns true if this spread effects the position of any outrights
static bool residual_pos_spread(const api::Security &s);
};
};
} // namespace admin
} // namespace bts
@@ -65,6 +65,7 @@ function(get_cpp_sources dest)
domain/DomainSecurityUtils.cpp
domain/SecurityCriteria.cpp
domain/SecurityCriteriaClause.cpp
domain/synthetic_book_side.cpp
domain_model/Security.cpp
domain_model/SecurityTests.cpp
domain_model/sod.cpp
@@ -64,6 +64,7 @@ function(get_cpp_sources dest)
domain/DomainSecurityUtils.cpp
domain/SecurityCriteria.cpp
domain/SecurityCriteriaClause.cpp
domain/synthetic_book_side.cpp
domain_model/Security.cpp
domain_model/SecurityTests.cpp
domain_model/sod.cpp
@@ -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"
-2
View File
@@ -39,8 +39,6 @@ LadderKnobs {
security_timeout_secs: 60
use_algo_param_grid: true
max_mbo_orders: 200
lighten_market_cells: true
mimic_tt: true
scrollable_tick_window_size: 256
}
}
+18
View File
@@ -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;
}
+4 -4
View File
@@ -82,7 +82,7 @@ message LadderKnobs {
bool show_scrollbar = 9;
// DJ wants bid/ask cells with markets (i.e., size > 0) to have a
// lighter background than those without markets.
bool lighten_market_cells = 10;
// bool lighten_market_cells = 10;
// For market cell backgrounds, 0 means 'ignore this color setting' (i.e.,
// employ the ladder's default behavior instead).
@@ -97,9 +97,9 @@ message LadderKnobs {
// Pure Red : 0xFFFF0000
// Pure Blue : 0xFF00FF00
// Pure Green : 0xFF0000FF
uint32 bid_market_cell_background = 11;
uint32 ask_market_cell_background = 12;
bool mimic_tt = 13;
// uint32 bid_market_cell_background = 11;
// uint32 ask_market_cell_background = 12;
// bool mimic_tt = 13;
// Defines the number of price rows rendered by the ladder. The smaller this
// window is, the cheaper it is to render the ladder grid (redraw prices,
// sizes, etc.). However, if the window is too small, scrolling may appear
+2 -2
View File
@@ -1,10 +1,10 @@
#pragma once
#include "BookLevel.h"
#include "BookLevelComp.h"
#include "BookSide.h"
#include "SecurityID.h"
#include "Side.h"
#include "lib/io/JsonReader.h"
#include <boost/container/small_vector.hpp>
#include <nlohmann/json.hpp>
+30
View File
@@ -33,6 +33,11 @@ class BookLevel {
void from_json(const nlohmann::json &j, BookLevel &bl);
void to_json(nlohmann::json &j, const BookLevel &bl);
constexpr bool operator==(const BookLevel &lhs, const BookLevel &rhs);
constexpr bool operator!=(const BookLevel &lhs, const BookLevel &rhs);
std::ostream &operator<<(std::ostream &out, const BookLevel &lvl) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
@@ -70,4 +75,29 @@ inline void to_json(nlohmann::json &j, const BookLevel &bl) {
};
}
constexpr bool operator==(const BookLevel &lhs, const BookLevel &rhs) {
return lhs.direct_quantity == rhs.direct_quantity &&
lhs.total_quantity == rhs.total_quantity &&
lhs.n_orders == rhs.n_orders &&
(lhs.price == rhs.price ||
(lhs.price.is_nan() && rhs.price.is_nan()));
}
constexpr bool operator!=(const BookLevel &lhs, const BookLevel &rhs) {
return !(lhs == rhs);
}
inline std::ostream &operator<<(
std::ostream &out, const BookLevel &lvl) noexcept {
out << lvl.price << " (" << lvl.direct_quantity;
if (lvl.direct_quantity != lvl.total_quantity) {
out << "/" << lvl.total_quantity;
}
if (lvl.n_orders != 0) {
out << ", " << lvl.n_orders;
}
out << ")";
return out;
}
} // namespace bts::domain
+31 -7
View File
@@ -1,18 +1,17 @@
#pragma once
#include "BookLevel.h"
#include "BookLevelComp.h"
#include "BookSide_PopulatedIterator.h"
#include "PriceComp.h"
#include "SecurityID.h"
#include "Side.h"
#include "lib/io/JsonReader.h"
#include <algorithm>
#include <boost/container/small_vector.hpp>
#include <initializer_list>
#include <iostream>
#include <iterator>
#include <limits>
#include <random>
#include <vector>
namespace bts::domain {
@@ -37,6 +36,8 @@ class BookSideT {
BookSideT &operator=(BookSideT &&) noexcept = default;
~BookSideT() = default;
constexpr BookLevel *data();
constexpr const BookLevel *data() const;
[[nodiscard]] constexpr BookLevel const &top() const;
constexpr BookLevel const &operator[](size_t i) const;
constexpr BookLevel &operator[](size_t i);
@@ -196,6 +197,16 @@ constexpr size_t BookSideT<SZ>::max_depth() noexcept {
return SZ;
}
template <std::size_t SZ>
constexpr BookLevel *BookSideT<SZ>::data() {
return _book.data();
}
template <std::size_t SZ>
constexpr const BookLevel *BookSideT<SZ>::data() const {
return _book.data();
}
template <std::size_t SZ>
constexpr const BookLevel &BookSideT<SZ>::top() const {
return _book[0];
@@ -234,12 +245,14 @@ BookSideT<SZ>::end() const noexcept {
}
template <std::size_t SZ>
constexpr typename BookSideT<SZ>::ReverseIterator BookSideT<SZ>::rbegin() noexcept {
constexpr typename BookSideT<SZ>::ReverseIterator
BookSideT<SZ>::rbegin() noexcept {
return _book.rbegin();
}
template <std::size_t SZ>
constexpr typename BookSideT<SZ>::ReverseIterator BookSideT<SZ>::rend() noexcept {
constexpr typename BookSideT<SZ>::ReverseIterator
BookSideT<SZ>::rend() noexcept {
return _book.rend();
}
@@ -250,12 +263,23 @@ constexpr size_t BookSideT<SZ>::size() const noexcept {
template <std::size_t SZ>
constexpr bool BookSideT<SZ>::operator==(BookSideT const &rhs) const {
return _book == rhs._book;
std::size_t i = 0;
for (; i < size(); i++) {
const BookLevel &l = (*this)[i];
if (i == rhs.size()) {
return l.is_empty();
}
const BookLevel &r = rhs[i];
if (l != r) {
return l.is_empty() && r.is_empty();
}
}
return i == rhs.size() || rhs[i].is_empty();
}
template <std::size_t SZ>
constexpr bool BookSideT<SZ>::operator!=(BookSideT const &rhs) const {
return _book != rhs._book;
return !(*this == rhs);
}
template <std::size_t SZ>
+77 -7
View File
@@ -1,16 +1,14 @@
#include "DomainSecurityUtils.h"
#include "FutureSecurity.h"
#include "OptionSecurity.h"
#include "SecurityCommon.h"
#include "SpotSecurity.h"
#include "SpreadSecurity.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/domain/TradeRecord.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/md/IntPrice.h"
#include "lib/md/PriceSize.h"
#include "lib/md/protos/co2.pb.h"
#include "lib/utils/bts_assert.h"
// #include "lib/domain/SecurityID.h"
// #include "lib/utils/overloaded.h"
// #include <fmt/format.h>
// #include <variant>
namespace bts::domain {
@@ -62,7 +60,7 @@ bts::api::Venue DomainSecurityUtils::convert(api::domain::Venue venue) {
case DOMAIN_VENUE(CBOE):
return api::Venue::VENUE_CBOE;
case DOMAIN_VENUE(TAIFEX):
return api::Venue::VENUE_TAIFEX;
return api::Venue::VENUE_TAIFEX;
case DOMAIN_VENUE(FTX):
return api::Venue::VENUE_FTX;
case DOMAIN_VENUE(BINANCE):
@@ -219,4 +217,76 @@ bool DomainSecurityUtils::get_security_desc(
return true;
}
domain::Price DomainSecurityUtils::convert(md::IntPrice iprice) {
int64_t mantissa = iprice.mantissa();
int8_t exp_diff = iprice.exponent() - domain::Price::exponent();
if (exp_diff != 0) {
mantissa *= std::pow(10, exp_diff);
}
return domain::Price{mantissa};
}
BookLevel DomainSecurityUtils::convert(md::PriceSize ps) {
BookLevel rv;
convert(ps, rv);
return rv;
}
void DomainSecurityUtils::convert(md::PriceSize ps, BookLevel &res) {
res.price = convert(ps.int_price());
res.total_quantity.value() = ps.size();
res.direct_quantity.value() = ps.direct_size();
res.n_orders = 0;
}
void DomainSecurityUtils::convert(
const bts::domain::BookSide &book_side,
bts::md::PriceSize *res,
size_t res_size) {
convert(book_side.data(), book_side.size(), res, res_size);
}
void DomainSecurityUtils::convert(
const BookLevel *book_side,
size_t size,
md::PriceSize *res,
size_t res_size) {
size_t sz = std::min(size, res_size);
size_t i = 0;
for (; i < sz; i++) {
// WARNING: Implied size is discarded here. There would be data loss anyways, since
// bts::md::PriceSize's implied size is a uint, and domain::BookLevel's is a float.
res[i] = md::PriceSize{
md::IntPrice{
book_side[i].price.mantissa(), book_side[i].price.exponent()},
book_side[i].total_quantity.value()};
}
// Set remaining levels to empty.
for (; i < res_size; i++) {
res[i] = bts::md::PriceSize{};
}
}
void DomainSecurityUtils::convert(
const md::PriceSize *book_side, std::size_t len, BookSide &res) {
if (res.size() < len) {
len = res.size();
}
while (len-- != 0) {
convert(book_side[len], res[len]);
}
}
void DomainSecurityUtils::convert(
const co2::TradeMessage &tm, TradeRecord &tr) {
tr.sec_id.value() = tm.key();
tr.time_stamp = core::TimeStamp::from_nanos(tm.time());
tr.agg_side = tm.aggressor_side() == co2::AggressorSide::AGGRESSOR_SIDE_BUY
? AggressorSide::Buy
: AggressorSide::Sell;
tr.trade_quantity.value() = tm.size();
tr.trade_price =
convert(md::IntPrice{tm.mantissa(), static_cast<int8_t>(tm.exponent())});
}
} // namespace bts::domain
+29 -4
View File
@@ -1,18 +1,28 @@
#pragma once
#include "FutureSecurity.h"
#include "OptionSecurity.h"
#include "SpotSecurity.h"
#include "SpreadSecurity.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/domain/SecurityID.h"
#include "lib/domain/BookSide.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/utils/overloaded.h"
#include <fmt/format.h>
#include <variant>
namespace bts::co2 {
class TradeMessage;
}
namespace bts::md {
class IntPrice;
class PriceSize;
} // namespace bts::md
namespace bts::domain {
class TradeRecord;
class DomainSecurityUtils {
public:
static api::domain::Venue convert(bts::api::Venue venue);
@@ -29,6 +39,21 @@ class DomainSecurityUtils {
static std::string get_exchange_id(const bts::api::Security &s);
static bool get_security_desc(
const bts::api::Security &security, std::string &desc);
static Price convert(md::IntPrice iprice);
static BookLevel convert(md::PriceSize ps);
static void convert(md::PriceSize ps, BookLevel &res);
static void convert(
const BookSide &book_side, md::PriceSize *res, std::size_t res_size);
static void convert(
const BookLevel *book_side,
std::size_t size,
md::PriceSize *res,
std::size_t res_size);
static void convert(
const md::PriceSize *book_side, std::size_t len, BookSide &res);
static void convert(const co2::TradeMessage &tm, TradeRecord &tr);
};
} // namespace bts::domain
+2 -2
View File
@@ -1,13 +1,13 @@
#pragma once
#include "AccountID.h"
#include "Price.h"
#include "Quantity.h"
#include "SecurityID.h"
#include "Side.h"
#include "TimeInForce.h"
#include "UserID.h"
#include "lib/domain/cme/GlbxOrderDefinition.h"
#include "lib/domain/deribit/DeribitOrderDefinition.h"
#include <string>
#include <variant>
+20 -21
View File
@@ -5,7 +5,6 @@
#include "lib/logging/logging.h"
#include "lib/utils/ValueWrapper.h"
#include <cstdint>
#include <nlohmann/json.hpp>
namespace bts::domain {
@@ -77,7 +76,7 @@ inline void to_json(nlohmann::json &j, const Quantity &sec_id) {
return lhs; \
}
#define QTY_UINT32_BIN_OP(result_t, op) \
#define QTY_VALUE_BIN_OP(result_t, op) \
constexpr result_t operator op(Quantity lhs, Quantity::Value rhs) { \
return result_t(lhs.value() op rhs); /*NOLINT*/ \
} \
@@ -85,7 +84,7 @@ inline void to_json(nlohmann::json &j, const Quantity &sec_id) {
return result_t(lhs op rhs.value()); \
}
#define QTY_UINT32_BIN_REF_OP(op) \
#define QTY_VALUE_BIN_REF_OP(op) \
inline Quantity &operator op(Quantity &lhs, Quantity::Value rhs) { \
lhs.value() op rhs; \
return lhs; \
@@ -107,29 +106,29 @@ QTY_QTY_BIN_REF_OP(*=)
QTY_QTY_BIN_REF_OP(/=)
// QTY_QTY_BIN_REF_OP(%=)
QTY_UINT32_BIN_OP(bool, !=)
QTY_UINT32_BIN_OP(bool, ==)
QTY_UINT32_BIN_OP(bool, <)
QTY_UINT32_BIN_OP(bool, <=)
QTY_UINT32_BIN_OP(bool, >)
QTY_UINT32_BIN_OP(bool, >=)
QTY_VALUE_BIN_OP(bool, !=)
QTY_VALUE_BIN_OP(bool, ==)
QTY_VALUE_BIN_OP(bool, <)
QTY_VALUE_BIN_OP(bool, <=)
QTY_VALUE_BIN_OP(bool, >)
QTY_VALUE_BIN_OP(bool, >=)
QTY_UINT32_BIN_OP(Quantity, +)
QTY_UINT32_BIN_OP(Quantity, -)
QTY_UINT32_BIN_OP(Quantity, *)
QTY_UINT32_BIN_OP(Quantity, /)
// QTY_UINT32_BIN_OP(Quantity, %)
QTY_VALUE_BIN_OP(Quantity, +)
QTY_VALUE_BIN_OP(Quantity, -)
QTY_VALUE_BIN_OP(Quantity, *)
QTY_VALUE_BIN_OP(Quantity, /)
// QTY_VALUE_BIN_OP(Quantity, %)
QTY_UINT32_BIN_REF_OP(+=)
QTY_UINT32_BIN_REF_OP(-=)
QTY_UINT32_BIN_REF_OP(*=)
QTY_UINT32_BIN_REF_OP(/=)
// QTY_UINT32_BIN_REF_OP(%=)
QTY_VALUE_BIN_REF_OP(+=)
QTY_VALUE_BIN_REF_OP(-=)
QTY_VALUE_BIN_REF_OP(*=)
QTY_VALUE_BIN_REF_OP(/=)
// QTY_VALUE_BIN_REF_OP(%=)
#undef QTY_QTY_BIN_OP
#undef QTY_QTY_BIN_REF_OP
#undef QTY_UINT32_BIN_OP
#undef QTY_UINT32_BIN_REF_OP
#undef QTY_VALUE_BIN_OP
#undef QTY_VALUE_BIN_REF_OP
} // namespace bts::domain
-1
View File
@@ -1,6 +1,5 @@
#include "Security.h"
#include "DomainSecurityUtils.h"
#include "lib/core/TableFormatter.h"
#include "lib/domain/FutureSecurity.h"
#include "lib/domain/OptionSecurity.h"
#include "lib/domain/SpotSecurity.h"
+3 -13
View File
@@ -1,22 +1,12 @@
#pragma once
#include "DomainSecurityUtils.h"
#include "Price.h"
#include "SecurityID.h"
#include "lib/api/domain/CallPut.pb.h"
#include "lib/api/domain/SecurityType.pb.h"
#include "lib/api/domain/Venue.h"
#include "lib/api/domain/Venue.pb.h"
#include "lib/core/TimeStamp.h"
#include "lib/domain/FutureSecurity.h"
#include "lib/domain/LegDefinition.h"
#include "lib/domain/OptionSecurity.h"
#include "lib/domain/Quantity.h"
#include "lib/domain/Side.h"
#include "lib/domain/SpreadSecurity.h"
#include "lib/domain/DomainSecurityUtils.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include "lib/utils/bts_assert.h"
#include <limits>
#include <nlohmann/json.hpp>
#include <string>
@@ -40,7 +30,7 @@ class SecurityCommon {
double min_quantity_increment = 0;
double min_quantity = 0;
double max_quantity = std::numeric_limits<double>::max();
double cash_per_point =1;
double cash_per_point = 1;
SecurityCommon() {}
SecurityCommon(const bts::api::Security &s)
@@ -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>
+5
View File
@@ -1,4 +1,7 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include <ostream>
namespace bts::domain {
@@ -153,3 +156,5 @@ constexpr Side operator*(Side lhs, Side::Value rhs) noexcept {
}
} // namespace bts::domain
FMT_IMPL(bts::domain::Side, s, "{}", s.name());
+2 -14
View File
@@ -1,12 +1,12 @@
#pragma once
#include "AggressorSide.h"
#include "Price.h"
#include "Quantity.h"
#include "SecurityID.h"
#include "lib/core/TimeStamp.h"
#include "lib/logging/logging.h"
#include "lib/md/IntPrice.h"
#include "lib/md/protos/co2.pb.h"
#include <nlohmann/json.hpp>
namespace bts::domain {
@@ -18,8 +18,6 @@ class TradeRecord {
AggressorSide agg_side = {};
Quantity trade_quantity;
Price trade_price;
TradeRecord() {}
inline TradeRecord(const bts::co2::TradeMessage &tm);
};
void from_json(const nlohmann::json &j, TradeRecord &tr);
@@ -29,16 +27,6 @@ void to_json(nlohmann::json &j, const TradeRecord &tr);
// Definition
//////////////////////////////////////////////////
inline TradeRecord::TradeRecord(const bts::co2::TradeMessage &tm)
: sec_id(tm.key()), time_stamp(tm.time()),
agg_side(
tm.aggressor_side() == co2::AggressorSide::AGGRESSOR_SIDE_BUY
? AggressorSide{AggressorSide::Buy}
: AggressorSide{AggressorSide::Sell}),
trade_quantity(tm.size()),
trade_price(Price::from_double(
bts::md::IntPrice(tm.mantissa(), tm.exponent()).price())) {}
inline void from_json(const nlohmann::json &j, TradeRecord &tr) {
io::JsonReader jr{&j};
// clang-format off
+107
View File
@@ -0,0 +1,107 @@
#pragma once
#include "lib/domain/BookSide.h"
#include "lib/domain/Quantity.h"
namespace bts::domain {
class BookSideConsumer {
// Use pointer so copying is cheap
const BookLevel *levels_;
std::size_t size_;
std::size_t curr_{0};
Quantity curr_consumed_{0};
public:
BookSideConsumer() = default;
BookSideConsumer(const BookLevel *levels, std::size_t size)
: levels_(levels), size_(size) {}
template <std::size_t SZ>
BookSideConsumer(
const BookSideT<SZ> *levels,
std::size_t curr = 0,
Quantity curr_consumed = Quantity{0});
BookSideConsumer(const BookSideConsumer &other) = default;
BookSideConsumer &operator=(BookSideConsumer &&other) = default;
BookSideConsumer &operator=(const BookSideConsumer &other) = default;
const BookLevel &operator[](std::size_t idx) const;
const BookLevel *levels() const { return levels_; }
size_t size() const;
bool is_done() const;
void reset();
Quantity available(std::size_t level_limit = 1) const;
std::pair<Quantity, Price> consume(Quantity quantity);
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
template <std::size_t SZ>
inline BookSideConsumer::BookSideConsumer(
const BookSideT<SZ> *levels, std::size_t curr, Quantity curr_consumed)
: levels_(levels), curr_(curr), curr_consumed_(curr_consumed) {}
inline const BookLevel &BookSideConsumer::operator[](std::size_t idx) const {
return levels_[idx];
}
inline size_t BookSideConsumer::size() const { return size_; }
inline bool BookSideConsumer::is_done() const {
if (curr_ >= size_) {
return true;
}
if (curr_ == size_ - 1) {
return curr_consumed_ >= levels_[curr_].total_quantity;
}
return curr_consumed_ >= levels_[curr_].total_quantity &&
levels_[curr_ + 1].total_quantity == 0;
}
inline void BookSideConsumer::reset() {
curr_ = 0;
curr_consumed_.value() = 0;
}
inline Quantity BookSideConsumer::available(std::size_t level_limit) const {
std::size_t end = size_ > curr_ + level_limit ? curr_ + level_limit : size_;
Quantity rv{0};
for (std::size_t i = curr_; i < end; i++) {
Quantity level_qty = levels_[i].total_quantity;
if (i == curr_) {
level_qty -= curr_consumed_;
}
rv += level_qty;
}
return rv;
}
inline std::pair<Quantity, Price> BookSideConsumer::consume(Quantity quantity) {
Quantity consumed{0};
Price price = Price::zero();
for (; curr_ < size_; curr_++) {
Quantity level_qty = levels_[curr_].total_quantity - curr_consumed_;
if (level_qty == 0) {
break;
}
Quantity rem = quantity - consumed;
if (level_qty > rem) {
curr_consumed_ += rem;
consumed = quantity;
price += levels_[curr_].price * rem.value();
break;
}
curr_consumed_.value() = 0;
consumed += level_qty;
price += levels_[curr_].price * level_qty.value();
}
return {consumed, price};
}
} // namespace bts::domain
+116
View File
@@ -0,0 +1,116 @@
#include "synthetic_book_side.h"
#include "lib/domain/book_side_consumer.h"
namespace bts::domain {
void SyntheticBookSide::copy_(const SyntheticBookSide &other) noexcept {
if (this == &other) {
return;
}
leg_qty_.value() = other.leg_qty_.value();
// Copy 'data_'
if (other.data_ != nullptr) {
// Clear 'own_data_'
if (own_data_ != nullptr) {
delete[] own_data_;
own_data_ = nullptr;
}
size_ = other.size_;
data_ = other.data_;
return;
}
data_ = nullptr;
if (other.size_ == 0) {
// Clear 'own_data_'
if (own_data_ != nullptr) {
delete[] own_data_;
own_data_ = nullptr;
}
size_ = 0;
return;
}
// Copy 'own_data_'
if (own_data_ == nullptr) {
own_data_ = new BookLevel[other.size_];
} else if (size_ != other.size_) {
delete[] own_data_;
own_data_ = new BookLevel[other.size_];
}
size_ = other.size_;
std::memcpy(own_data_, other.own_data_, size_ * sizeof(BookLevel));
}
void SyntheticBookSide::move_(SyntheticBookSide &&other) noexcept {
if (own_data_ != nullptr) {
delete[] own_data_;
}
leg_qty_.value() = other.leg_qty_.value();
data_ = other.data_;
size_ = other.size_;
own_data_ = other.own_data_;
other.leg_qty_.value() = 0;
other.data_ = nullptr;
other.own_data_ = nullptr;
other.size_ = 0;
}
SyntheticBookSide SyntheticBookSide::add_leg(
SyntheticBookSide other_leg,
BookLevel *synth_bk,
size_t synth_bk_size) const noexcept {
if (leg_qty_.value() == 0) {
return other_leg;
}
if (other_leg.leg_qty_.value() == 0) {
return *this;
}
SyntheticBookSide rv;
rv.leg_qty_.value() = 1;
rv.size_ = synth_bk_size;
if (synth_bk == nullptr) {
rv.own_data_ = synth_bk = new BookLevel[synth_bk_size];
} else {
rv.data_ = synth_bk;
}
BookSideConsumer l1{data(), size_};
BookSideConsumer l2{other_leg.data(), other_leg.size_};
Quantity l1_qty = leg_qty_;
Quantity l2_qty = other_leg.leg_qty_;
std::size_t i = 0;
for (; !l1.is_done() && !l2.is_done() && i < synth_bk_size; i++) {
Quantity leg2_per_leg1 = (l2_qty / l1_qty);
if (leg2_per_leg1 < 0) {
leg2_per_leg1.value() = -leg2_per_leg1.value();
}
Quantity leg1_per_leg2 = (l1_qty / l2_qty);
if (leg1_per_leg2 < 0) {
leg1_per_leg2.value() = -leg1_per_leg2.value();
}
Quantity avail1 = l1.available();
Quantity avail2 = l2.available();
Quantity take1;
Quantity take2;
if ((avail1 / leg1_per_leg2) < (avail2 / leg2_per_leg1)) {
take1 = avail1;
take2 = avail1 * leg2_per_leg1;
} else {
take1 = avail2 * leg1_per_leg2;
take2 = avail2;
}
const auto [qty1, cost1] = l1.consume(take1);
const auto [qty2, cost2] = l2.consume(take2);
Price price1 = (cost1 / qty1.value()) * l1_qty.value();
Price price2 = (cost2 / qty2.value()) * l2_qty.value();
auto &synth_lvl = synth_bk[i];
synth_lvl.price = price1 + price2;
synth_lvl.total_quantity = qty1;
synth_lvl.direct_quantity = qty1;
synth_lvl.n_orders = 0;
}
for (; i < synth_bk_size; i++) {
synth_bk[i] = BookLevel::empty();
}
return rv;
}
} // namespace bts::domain
+112
View File
@@ -0,0 +1,112 @@
#pragma once
#include "lib/domain/BookLevel.h"
#include "lib/domain/Quantity.h"
#include <cstddef>
#include <cstring>
namespace bts::domain {
class SyntheticBookSide {
Quantity leg_qty_{0};
// Either 'data_' or 'own_data_' can be non-null (not both). Only the latter
// is deleted in the destructor.
const BookLevel *data_{nullptr};
BookLevel *own_data_{nullptr};
std::size_t size_{0};
void copy_(const SyntheticBookSide &other) noexcept;
void move_(SyntheticBookSide &&other) noexcept;
public:
constexpr SyntheticBookSide() = default;
constexpr SyntheticBookSide(
Quantity leg_qty, const BookLevel *book_side, std::size_t size);
constexpr ~SyntheticBookSide();
SyntheticBookSide(const SyntheticBookSide &other);
SyntheticBookSide(SyntheticBookSide &&other);
SyntheticBookSide &operator=(const SyntheticBookSide &other);
SyntheticBookSide &operator=(SyntheticBookSide &&other);
constexpr std::size_t size() const;
constexpr const BookLevel *data() const;
/**
* Adds a leg to this one (calls can be chained to get the depth for N legs).
*
* @return SyntheticBookSide whose levels constitute the synthetic depth of
* this and the specified @p leg. Its quantity is always 1.
*/
SyntheticBookSide add_leg(SyntheticBookSide leg) const noexcept;
SyntheticBookSide add_leg(
SyntheticBookSide other_leg,
BookLevel *synth_bk,
size_t synth_bk_size) const noexcept;
/**
* Syntactic sugar for `add_leg`
*/
SyntheticBookSide operator+(SyntheticBookSide other) const noexcept;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
constexpr SyntheticBookSide::SyntheticBookSide(
Quantity leg_qty, const BookLevel *data, std::size_t size)
: leg_qty_(leg_qty), data_(data), own_data_(nullptr), size_(size) {}
constexpr SyntheticBookSide::~SyntheticBookSide() {
leg_qty_.value() = 0;
data_ = nullptr;
if (own_data_ != nullptr) {
delete[] own_data_;
own_data_ = nullptr;
}
size_ = 0;
}
inline SyntheticBookSide::SyntheticBookSide(const SyntheticBookSide &other) {
copy_(other);
}
inline SyntheticBookSide::SyntheticBookSide(SyntheticBookSide &&other) {
move_(std::move(other));
}
inline SyntheticBookSide &SyntheticBookSide::operator=(
const SyntheticBookSide &other) {
if (this != &other) {
copy_(other);
}
return *this;
}
inline SyntheticBookSide &SyntheticBookSide::operator=(
SyntheticBookSide &&other) {
if (this != &other) {
move_(std::move(other));
}
return *this;
}
constexpr std::size_t SyntheticBookSide::size() const { return size_; }
constexpr const BookLevel *SyntheticBookSide::data() const {
return data_ == nullptr ? own_data_ : data_;
}
inline SyntheticBookSide SyntheticBookSide::add_leg(
SyntheticBookSide other_leg) const noexcept {
return add_leg(other_leg, nullptr, 10);
}
inline SyntheticBookSide SyntheticBookSide ::operator+(
SyntheticBookSide other) const noexcept {
return add_leg(other);
}
} // namespace bts::domain
+1 -4
View File
@@ -1,10 +1,8 @@
#include "Security.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/config/config.h"
#include "lib/config/protos/orders_fills_cleanup.pb.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/md/common.h"
#include "lib/utils/datetime.h"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <google/protobuf/text_format.h>
@@ -13,7 +11,6 @@ namespace bts {
namespace securities {
using bts::api::Security;
using bts::api::Venue;
using std::optional;
api::SecurityType security_type(api::Security const &security) {
-2
View File
@@ -7,10 +7,8 @@
#include "lib/domain_model/Venue.h"
#include "lib/domain_model/common.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/datetime.h"
#include <cmath>
#include <memory>
namespace boost {
namespace gregorian {
+12
View File
@@ -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 {
+28 -1
View File
@@ -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");
+11 -3
View File
@@ -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:
+4 -4
View File
@@ -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");
+255 -133
View File
@@ -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
+2 -1
View File
@@ -1,7 +1,8 @@
#pragma once
#include "lib/md/SecurityIndex.h"
#include <cstdint>
#include <string>
namespace bts::md {
+3 -2
View File
@@ -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 {
-3
View File
@@ -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>
-4
View File
@@ -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>
-4
View File
@@ -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>
+1 -1
View File
@@ -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 {
+1 -5
View File
@@ -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 -3
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -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>
-1
View File
@@ -10,7 +10,6 @@
#include "lib/os/os.h"
#include "lib/utils/SharedList.h"
#include "lib/utils/WaitFreeQueue.h"
#include <iomanip>
using namespace std;
-2
View File
@@ -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 {
-2
View File
@@ -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;
-8
View File
@@ -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 {
-2
View File
@@ -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>
+1 -1
View File
@@ -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 {
-5
View File
@@ -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>
+3 -11
View File
@@ -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 -2
View File
@@ -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