Compare commits
107
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3566155a3 | ||
|
|
a4e39afbb9 | ||
|
|
efce01d28e | ||
|
|
d362022b89 | ||
|
|
e7d751fb68 | ||
|
|
50eefc65d4 | ||
|
|
8770de4a87 | ||
|
|
e799b6e24b | ||
|
|
69e338c1ca | ||
|
|
e8bb64a80f | ||
|
|
8e2ede596f | ||
|
|
9772d0eb77 | ||
|
|
687e2477c0 | ||
|
|
1bd0ba9950 | ||
|
|
ddabc744f2 | ||
|
|
74b88b003e | ||
|
|
aaf1be09c2 | ||
|
|
e3bd9a457c | ||
|
|
59f62b6ec9 | ||
|
|
b46e75f7d7 | ||
|
|
3f100feaca | ||
|
|
093da2a938 | ||
|
|
2f17c2b8c5 | ||
|
|
33bfdc517a | ||
|
|
d17be48e6d | ||
|
|
cbaa1df641 | ||
|
|
8c23665d65 | ||
|
|
c7db105533 | ||
|
|
3f9923239e | ||
|
|
9ae9451359 | ||
|
|
c33e5ddb02 | ||
|
|
54388d3b92 | ||
|
|
17ada3c57c | ||
|
|
046f412ec4 | ||
|
|
c94d11f525 | ||
|
|
2b69846381 | ||
|
|
bdd5e08b60 | ||
|
|
7c9330650d | ||
|
|
98fca5ad7f | ||
|
|
8a0fcce90d | ||
|
|
112527f9ed | ||
|
|
fc1e20538d | ||
|
|
83acbecf55 | ||
|
|
06c494291c | ||
|
|
fe74aed048 | ||
|
|
b94cdb561e | ||
|
|
06bd8791c0 | ||
|
|
b5850cdb02 | ||
|
|
545d76142f | ||
|
|
edc2cea60e | ||
|
|
e6466f8309 | ||
|
|
501be8d6a5 | ||
|
|
7dea9b3792 | ||
|
|
a3f49b1ce8 | ||
|
|
765237d701 | ||
|
|
97c52410a2 | ||
|
|
8b43d93e26 | ||
|
|
14baa7d62e | ||
|
|
a054f4cc63 | ||
|
|
5311128607 | ||
|
|
075acdc8eb | ||
|
|
e1fdf6306b | ||
|
|
3b9fb2759b | ||
|
|
3f89d3b7f7 | ||
|
|
a6dd0d842d | ||
|
|
d050d2d19a | ||
|
|
942d00a8b1 | ||
|
|
31cb25ac2e | ||
|
|
cbb43a22de | ||
|
|
9e7b4849f0 | ||
|
|
6e72890153 | ||
|
|
c238fce057 | ||
|
|
b8f5db4ee2 | ||
|
|
87c8b9862a | ||
|
|
fbc2d8633d | ||
|
|
d12d798fa5 | ||
|
|
e75be2da2f | ||
|
|
3ae3a7094e | ||
|
|
bafa2f71ba | ||
|
|
63bd29a49e | ||
|
|
5fdc9ca48b | ||
|
|
a19e7bc7b4 | ||
|
|
04b0a89644 | ||
|
|
363a43cb8b | ||
|
|
c9f9c5f0f6 | ||
|
|
ab84a05644 | ||
|
|
4681f22e71 | ||
|
|
a25e055e35 | ||
|
|
2386612b51 | ||
|
|
3606dac145 | ||
|
|
bf936c9d13 | ||
|
|
1aeeb7170e | ||
|
|
78ad8a3aed | ||
|
|
5ca8dbafaf | ||
|
|
b464b9466e | ||
|
|
7a5c676e99 | ||
|
|
1e8d699dd1 | ||
|
|
aadd2dd594 | ||
|
|
5163ca4cae | ||
|
|
b5d520d568 | ||
|
|
83e4465a3b | ||
|
|
54cfb343a8 | ||
|
|
4e9b360e23 | ||
|
|
16bc93b1a0 | ||
|
|
f56d4d9062 | ||
|
|
36cc186626 | ||
|
|
2e78af1756 |
No files matched your search
Vendored
+17
-17
@@ -363,7 +363,7 @@
|
||||
"${workspaceFolder}/src/DataStreamSuite/DataStreamQueryAnalyzer/Configurations/DataStreamQueryAnalyzer.json",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net7.0-windows"
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net8.0-windows"
|
||||
},
|
||||
},
|
||||
"icon": {
|
||||
@@ -383,7 +383,7 @@
|
||||
"${workspaceFolder}/src/DataStreamSuite/DataStreamQueryAnalyzer/Configurations/DataStreamQueryAnalyzer.json",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0-windows"
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0-windows"
|
||||
},
|
||||
},
|
||||
"icon": {
|
||||
@@ -403,7 +403,7 @@
|
||||
"${workspaceFolder}/src/DataStreamSuite/DataStreamQueryAnalyzer/Configurations/DataStreamQueryAnalyzer.json",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Release/bin/net7.0-windows"
|
||||
"cwd": "${workspaceFolder}/build/Release/bin/net8.0-windows"
|
||||
},
|
||||
},
|
||||
"icon": {
|
||||
@@ -427,7 +427,7 @@
|
||||
"${workspaceFolder}/src/DataStreamSuite/DataStreamPublisher/Configurations/${input:dataStreamPublisherConfig}.json",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net7.0"
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net8.0"
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
@@ -450,7 +450,7 @@
|
||||
"${workspaceFolder}/src/DataStreamSuite/DataStreamPublisher/Configurations/${input:dataStreamPublisherConfig}.json",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0"
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0"
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
@@ -473,7 +473,7 @@
|
||||
"${workspaceFolder}/src/DataStreamSuite/DataStreamPublisher/Configurations/${input:dataStreamPublisherConfig}.json",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Release/bin/net7.0"
|
||||
"cwd": "${workspaceFolder}/build/Release/bin/net8.0"
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
@@ -496,7 +496,7 @@
|
||||
"${workspaceFolder}/src/DataStreamSuite/PubSubServer/Configurations/${input:pubSubServerConfig}.json",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net7.0"
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net8.0"
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
@@ -519,7 +519,7 @@
|
||||
"${workspaceFolder}/src/DataStreamSuite/PubSubServer/Configurations/${input:pubSubServerConfig}.json",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0"
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0"
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
@@ -541,7 +541,7 @@
|
||||
"${workspaceFolder}/src/DataStreamSuite/PubSubServer/Configurations/${input:pubSubServerConfig}.json",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0"
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0"
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
@@ -564,7 +564,7 @@
|
||||
"${workspaceFolder}/src/DataStreamSuite/PubSubServer/Configurations/${input:pubSubServerConfig}.json",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Release/bin/net7.0"
|
||||
"cwd": "${workspaceFolder}/build/Release/bin/net8.0"
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
@@ -586,7 +586,7 @@
|
||||
"${workspaceFolder}/src/DataStreamSuite/PubSubServer/Configurations/${input:pubSubServerConfig}.json",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Release/bin/net7.0"
|
||||
"cwd": "${workspaceFolder}/build/Release/bin/net8.0"
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
@@ -601,7 +601,7 @@
|
||||
"windows": {
|
||||
"command": "Spark.exe",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net7.0-windows"
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net8.0-windows"
|
||||
},
|
||||
},
|
||||
"icon": {
|
||||
@@ -617,7 +617,7 @@
|
||||
"windows": {
|
||||
"command": "Spark.exe",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0-windows"
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0-windows"
|
||||
},
|
||||
},
|
||||
"icon": {
|
||||
@@ -633,7 +633,7 @@
|
||||
"windows": {
|
||||
"command": "Spark.exe",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Release/bin/net7.0-windows"
|
||||
"cwd": "${workspaceFolder}/build/Release/bin/net8.0-windows"
|
||||
},
|
||||
},
|
||||
"icon": {
|
||||
@@ -648,7 +648,7 @@
|
||||
"windows": {
|
||||
"command": "Spark.exe",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net7.0-windows"
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net8.0-windows"
|
||||
},
|
||||
},
|
||||
"icon": {
|
||||
@@ -673,7 +673,7 @@
|
||||
"${input:sparkConfigQuery}",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net7.0"
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net8.0"
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
@@ -754,7 +754,7 @@
|
||||
"${input:sparkTest}"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0",
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net8.0",
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace mbts.spark {
|
||||
Vega,
|
||||
Theta,
|
||||
Rho,
|
||||
Currency
|
||||
Currency,
|
||||
CashPerPoint,
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,8 @@ namespace bts.shell.market_view {
|
||||
public DoubleMinMaxState theta { get; set; }
|
||||
[ProtoMember(36)]
|
||||
public DoubleMinMaxState rho { get; set; }
|
||||
[ProtoMember(39)]
|
||||
public DoubleMinMaxState cash_per_point { get; set; }
|
||||
|
||||
public Func<MarketViewVM.Row, bool> build_predicate() {
|
||||
var pb = new ColumnPredicateBuilder<MarketViewVM.Row>();
|
||||
@@ -185,6 +187,7 @@ namespace bts.shell.market_view {
|
||||
pb.add_numeric(vega, r => r.vega);
|
||||
pb.add_numeric(theta, r => r.theta);
|
||||
pb.add_numeric(rho, r => r.rho);
|
||||
pb.add_numeric(cash_per_point, r => r.cash_per_point);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,7 @@ namespace mbts.spark {
|
||||
public bts.api.Venue venue => security_md.security.Exchange;
|
||||
public uint security_key => security_md.security.Id;
|
||||
public string currency => security_md.security.Currency;
|
||||
public double cash_per_point => security_md.security.ContractSpec.CashPerPoint;
|
||||
|
||||
public bool inside_bids => _inside_bids.value;
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
|
||||
@@ -146,6 +146,7 @@ namespace bts.shell.market_view {
|
||||
public static GridColumnSettings vega => _mk(Columns.Vega, "Vega", ColWidth.Greeks, false);
|
||||
public static GridColumnSettings theta => _mk(Columns.Theta, "Theta", ColWidth.Greeks, false);
|
||||
public static GridColumnSettings rho => _mk(Columns.Rho, "Rho", ColWidth.Greeks, false);
|
||||
public static GridColumnSettings cash_per_point => _mk(Columns.CashPerPoint, "CPP", ColWidth.Greeks, false);
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criteria {
|
||||
get {
|
||||
|
||||
@@ -198,6 +198,7 @@ namespace mbts.spark {
|
||||
add_col(ColKey.Vega, r => fmt_greek(r.vega), nameof(Row.vega), vega_filter, FilterType.DoubleMinMax, ColWidth.Greeks);
|
||||
add_col(ColKey.Theta, r => fmt_greek(r.theta), nameof(Row.theta), theta_filter, FilterType.DoubleMinMax, ColWidth.Greeks);
|
||||
add_col(ColKey.Rho, r => fmt_greek(r.rho), nameof(Row.rho), rho_filter, FilterType.DoubleMinMax, ColWidth.Greeks);
|
||||
add_col(ColKey.CashPerPoint, r => r.cash_per_point.ToString("0.00#####"), nameof(Row.cash_per_point), currency_filter, FilterType.DoubleMinMax, ColWidth.Greeks);
|
||||
|
||||
// row vm filter
|
||||
// Althoguh securities are filtered at the row 'model' level (SecurityMD), the column base filters have to be applied at the row vm level,
|
||||
@@ -436,6 +437,7 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> gamma_filter { get; } = new DoubleMinMax<Row>(r => r.gamma, nameof(Row.gamma));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> theta_filter { get; } = new DoubleMinMax<Row>(r => r.theta, nameof(Row.theta));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> rho_filter { get; } = new DoubleMinMax<Row>(r => r.rho, nameof(Row.rho));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> cash_per_point_filter { get; } = new DoubleMinMax<Row>(r => r.cash_per_point, nameof(Row.cash_per_point));
|
||||
|
||||
IEnumerable<IDataGridColumnFilter<Row>> _all_filters =>
|
||||
_filter_getters.Select(g => g(this));
|
||||
@@ -477,6 +479,7 @@ namespace mbts.spark {
|
||||
ColKey.Vega => wrap(r => r.vega),
|
||||
ColKey.Theta => wrap(r => r.theta),
|
||||
ColKey.Rho => wrap(r => r.rho),
|
||||
ColKey.CashPerPoint => wrap(r => r.cash_per_point),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
@@ -583,6 +586,7 @@ namespace mbts.spark {
|
||||
vega = vega_filter.get_state(),
|
||||
theta = theta_filter.get_state(),
|
||||
rho = rho_filter.get_state(),
|
||||
cash_per_point = cash_per_point_filter.get_state(),
|
||||
};
|
||||
|
||||
public void load_filters(MarketViewFilterStates filters) {
|
||||
@@ -625,6 +629,7 @@ namespace mbts.spark {
|
||||
vega_filter.load_state(filters.vega);
|
||||
theta_filter.load_state(filters.theta);
|
||||
rho_filter.load_state(filters.rho);
|
||||
cash_per_point_filter.load_state(filters.cash_per_point);
|
||||
}
|
||||
|
||||
static (string desc, Color fg) _fills_title(ColKey key) =>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.24.3" />
|
||||
<PackageReference Include="IronPython" Version="3.4.1" />
|
||||
<PackageReference Include="IronPython" Version="3.4.2" />
|
||||
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="NAudio" Version="2.0.1" />
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace bts.md {
|
||||
implied_size = implied_sz;
|
||||
price = prx;
|
||||
}
|
||||
|
||||
public PriceSize(Size sz, double prx)
|
||||
: this(sz, sz, 0, prx) { }
|
||||
|
||||
public override string ToString() {
|
||||
return $"{price} {size} {implied_size} ";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
#nullable enable
|
||||
namespace Spark.Api.MarketData;
|
||||
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.md;
|
||||
using bts.utils;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
public static class SyntheticBookCalculator {
|
||||
public static Book GetSyntheticBook(uint securityId, IEnumerable<(ContractLegImmutable, Book)> legDepths) {
|
||||
return new(
|
||||
securityId,
|
||||
GetSyntheticDepth(legDepths.Select(GetBidLegParams).ToList()).Select(ToPriceSize).ToList(),
|
||||
GetSyntheticDepth(legDepths.Select(GetAskLegParams).ToList()).Select(ToPriceSize).ToList());
|
||||
|
||||
PriceSize ToPriceSize(DepthLevel level) =>
|
||||
new((float)level.Quantity, level.Price);
|
||||
|
||||
(double Quantity, double TickSize, Book Book) GetBidLegParams((ContractLegImmutable, Book) ld) =>
|
||||
(ld.Item1.signed_quantity(), ld.Item1.Security.ContractSpec.LowTick, ld.Item2);
|
||||
|
||||
(double Quantity, double TickSize, Book Book) GetAskLegParams((ContractLegImmutable, Book) ld) =>
|
||||
(-ld.Item1.signed_quantity(), ld.Item1.Security.ContractSpec.LowTick, ld.Item2);
|
||||
}
|
||||
|
||||
public static List<DepthLevel> GetSyntheticDepth(IEnumerable<(ContractLegImmutable, Book)> legDepths) {
|
||||
return GetSyntheticDepth(
|
||||
legDepths.Select(getLegDepthConsumer).ToList());
|
||||
|
||||
LegDepthConsumer getLegDepthConsumer((ContractLegImmutable, Book) legDepth) {
|
||||
var (leg, book) = legDepth;
|
||||
Log.assert(leg.Quantity.is_real(), "Leg quantity must be real");
|
||||
Log.assert(leg.Security.HasContractSpec && leg.Security.ContractSpec.LowTick.is_real_positive(),
|
||||
"Leg security LowTick must be real and positive");
|
||||
return LegDepthConsumer.Create(leg.Quantity, leg.Security.ContractSpec.LowTick);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<DepthLevel> GetSyntheticDepth(IEnumerable<(double Quantity, double TickSize, Book Book)> legDepths) {
|
||||
return GetSyntheticDepth(
|
||||
legDepths.Select(getLegDepthConsumer).ToList());
|
||||
|
||||
LegDepthConsumer getLegDepthConsumer((double Quantity, double TickSize, Book Book) legDepth) {
|
||||
var (qty, tickSz, bk) = legDepth;
|
||||
Log.assert(qty.is_real(), "Leg quantity must be real");
|
||||
Log.assert(tickSz.is_real_positive(),
|
||||
"Leg tickSz must be real and positive");
|
||||
return LegDepthConsumer.Create(qty, tickSz);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<DepthLevel> GetSyntheticDepth(List<LegDepthConsumer> legs) {
|
||||
if (legs.Count == 0) {
|
||||
return new(0);
|
||||
}
|
||||
var synthLeg = legs.Skip(1).Aggregate(
|
||||
legs[0],
|
||||
(l1, l2) => new LegDepthConsumer(new(getSyntheticDepth(l1, l2).ToList())) {
|
||||
LegQuantity = 1,
|
||||
QuantityTick = MathUtils.lcm(l1.QuantityTick, l2.QuantityTick),
|
||||
});
|
||||
return synthLeg.Depth.Levels;
|
||||
}
|
||||
|
||||
static IEnumerable<DepthLevel> getSyntheticDepth(LegDepthConsumer leg1, LegDepthConsumer leg2) {
|
||||
while (!leg1.Depth.IsDone && !leg2.Depth.IsDone) {
|
||||
if (!leg1.LegQuantity.is_real() || !leg2.LegQuantity.is_real()) {
|
||||
throw new ArgumentException("Invalid leg quantities");
|
||||
}
|
||||
double leg2PerLeg1 = (leg2.LegQuantity / leg1.LegQuantity).abs();
|
||||
double leg1PerLeg2 = (leg1.LegQuantity / leg2.LegQuantity).abs();
|
||||
double avail1 = leg1.Depth.AvailableQuantity(1);
|
||||
double avail2 = leg2.Depth.AvailableQuantity(1);
|
||||
(double take1, double take2) = (avail1 / leg1PerLeg2) < (avail2 / leg2PerLeg1)
|
||||
? (avail1, avail1 * leg2PerLeg1)
|
||||
: (avail2 * leg1PerLeg2, avail2);
|
||||
var (qty1, cost1) = leg1.Depth.ConsumeQuantity(take1);
|
||||
var (qty2, cost2) = leg2.Depth.ConsumeQuantity(take2);
|
||||
Log.assert(qty2 / qty1 == leg2PerLeg1);
|
||||
var price1 = cost1 / qty1;
|
||||
var price2 = cost2 / qty2;
|
||||
price1 *= leg1.LegQuantity;
|
||||
price2 *= leg2.LegQuantity;
|
||||
yield return new(Price: price1 + price2, Quantity: qty1);
|
||||
}
|
||||
}
|
||||
|
||||
public record DepthLevel(double Price, double Quantity) {
|
||||
public override string ToString() {
|
||||
return $"{Price} ({Quantity})";
|
||||
}
|
||||
}
|
||||
|
||||
public class DepthLevelComparer : IEqualityComparer<DepthLevel> {
|
||||
readonly IEqualityComparer<double> priceComparer;
|
||||
readonly IEqualityComparer<double> quantityComparer;
|
||||
|
||||
public DepthLevelComparer(IEqualityComparer<double>? priceComparer = null, IEqualityComparer<double>? quantityComparer = null) {
|
||||
this.priceComparer = priceComparer ?? EqualityComparer<double>.Default;
|
||||
this.quantityComparer = quantityComparer ?? EqualityComparer<double>.Default;
|
||||
}
|
||||
|
||||
public bool Equals(DepthLevel? x, DepthLevel? y) {
|
||||
if (x is null) {
|
||||
return y is null;
|
||||
}
|
||||
return y is not null
|
||||
&& priceComparer.Equals(x.Price, y.Price)
|
||||
&& quantityComparer.Equals(x.Quantity, y.Quantity);
|
||||
}
|
||||
|
||||
public int GetHashCode([DisallowNull] DepthLevel x) =>
|
||||
HashCode.Combine(
|
||||
priceComparer.GetHashCode(x.Price),
|
||||
quantityComparer.GetHashCode(x.Quantity));
|
||||
}
|
||||
|
||||
public class DepthConsumer {
|
||||
public DepthConsumer() { }
|
||||
public DepthConsumer(params DepthLevel[] levels) : this(levels.AsEnumerable()) { }
|
||||
public DepthConsumer(IEnumerable<DepthLevel> levels) =>
|
||||
Levels.AddRange(levels);
|
||||
|
||||
public int Pos { get; private set; } = 0;
|
||||
public double CurrLevelConsumed { get; private set; } = 0;
|
||||
public List<DepthLevel> Levels { get; } = new();
|
||||
|
||||
public bool IsDone =>
|
||||
Pos == Levels.Count || (Pos == Levels.Count - 1 && Levels.Last().Quantity == CurrLevelConsumed);
|
||||
|
||||
public double AvailableQuantity(int levelLimit) {
|
||||
int end = Math.Min(Pos + levelLimit, Levels.Count);
|
||||
double rv = 0;
|
||||
for (int i = Pos; i < end; i++) {
|
||||
double levelQty = Levels[i].Quantity;
|
||||
if (i == Pos) {
|
||||
levelQty -= CurrLevelConsumed;
|
||||
}
|
||||
rv += levelQty;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public (double Qty, double Price) ConsumeQuantity(double quantity) {
|
||||
double consumed = 0;
|
||||
double price = 0;
|
||||
for (; Pos < Levels.Count; Pos++) {
|
||||
double levelQty = Levels[Pos].Quantity - CurrLevelConsumed;
|
||||
var rem = quantity - consumed;
|
||||
if (levelQty > rem) {
|
||||
CurrLevelConsumed += rem;
|
||||
consumed = quantity;
|
||||
price += rem * Levels[Pos].Price;
|
||||
break;
|
||||
}
|
||||
consumed += levelQty;
|
||||
price += levelQty * Levels[Pos].Price;
|
||||
CurrLevelConsumed = 0;
|
||||
}
|
||||
return (consumed, price);
|
||||
}
|
||||
}
|
||||
|
||||
public class LegDepthConsumer {
|
||||
public LegDepthConsumer() : this(new()) { }
|
||||
public LegDepthConsumer(DepthConsumer depth) =>
|
||||
Depth = depth;
|
||||
|
||||
public double LegQuantity { get; set; } = 1;
|
||||
public double QuantityTick { get; set; } = 1;
|
||||
public DepthConsumer Depth { get; } = new();
|
||||
|
||||
public static LegDepthConsumer Create(double legQuantity, double quantityTick, params DepthLevel[] levels) =>
|
||||
new(new(levels)) {
|
||||
LegQuantity = legQuantity,
|
||||
QuantityTick = quantityTick
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -124,8 +124,9 @@ namespace bts.data.ticks {
|
||||
do {
|
||||
// Note that the modulus operator is not used here due to rounding errors.
|
||||
// Such a rounding errror was the cause of the bug https://bluetradesys.atlassian.net/browse/SP-283.
|
||||
if (MathUtils.is_int(factored_base / raw_base))
|
||||
return new TickFactors(factored_base, factored_base / raw_base);
|
||||
if (MathUtils.is_int(factored_base / raw_base, MathUtils.API_DECIMAL_PRECISION)) {
|
||||
return new TickFactors(factored_base, Math.Round(factored_base / raw_base));
|
||||
}
|
||||
factored_base *= 10;
|
||||
} while (++count < 10);
|
||||
return null;
|
||||
|
||||
@@ -122,8 +122,7 @@ namespace bts.utils {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static T lcm<T>(IEnumerable<T> nums)
|
||||
where T : INumber<T>, IMultiplyOperators<T, int, T> {
|
||||
public static T lcm<T>(IEnumerable<T> nums) where T : INumber<T> {
|
||||
using var en = nums.Where(n => n != T.Zero).GetEnumerator();
|
||||
if (!en.MoveNext()) {
|
||||
return T.Zero;
|
||||
@@ -135,7 +134,7 @@ namespace bts.utils {
|
||||
return res;
|
||||
}
|
||||
|
||||
public static T lcm<T>(T a, T b) where T : INumber<T>, IMultiplyOperators<T, int, T> {
|
||||
public static T lcm<T>(T a, T b) where T : INumber<T> {
|
||||
T num1, num2;
|
||||
if (a > b) {
|
||||
num1 = a;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#nullable enable
|
||||
namespace bts.tests.security;
|
||||
|
||||
using bts.test;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using static Spark.Api.MarketData.SyntheticBookCalculator;
|
||||
|
||||
[TestGroup]
|
||||
class SyntheticBookTests {
|
||||
[Test]
|
||||
public void TestFractionalLegs() {
|
||||
var legs = new List<LegDepthConsumer>() {
|
||||
LegDepthConsumer.Create(1, 0.001, new(40, 0.205), new(38, 0.195)),
|
||||
LegDepthConsumer.Create(-0.5, 0.001, new(50, 0.365), new(55, 0.100)),
|
||||
};
|
||||
var synthBook = getSyntheticBook(legs);
|
||||
var expectedBook = new List<DepthLevel>() {
|
||||
new(15, 0.205),
|
||||
new(13, 0.195),
|
||||
};
|
||||
Assert.That(synthBook.SequenceEqual(expectedBook));
|
||||
legs = new List<LegDepthConsumer>() {
|
||||
LegDepthConsumer.Create(1, 0.001, new(50, 0.365), new(55, 0.100)),
|
||||
LegDepthConsumer.Create(-0.5, 0.001, new(40, 0.205), new(38, 0.195)),
|
||||
};
|
||||
synthBook = getSyntheticBook(legs);
|
||||
expectedBook = new() {
|
||||
new(30, 0.365),
|
||||
new(35, 0.045),
|
||||
new(36, 0.055),
|
||||
};
|
||||
var tf = new mbts.user.settings.TickFactors(1000, 1);
|
||||
var priceComp = tf.tick_equality_comparer();
|
||||
var qtyComp = new mbts.user.settings.TickFactors(1000000, 1).tick_equality_comparer();
|
||||
var levelComp = new DepthLevelComparer(priceComp, qtyComp);
|
||||
Assert.That(synthBook.SequenceEqual(expectedBook, levelComp));
|
||||
|
||||
legs = new List<LegDepthConsumer>() {
|
||||
LegDepthConsumer.Create(1, 0.001, new(50, 0.365), new(45, 0.100)),
|
||||
LegDepthConsumer.Create(1, 0.001, new(40, 0.205), new(38, 0.195)),
|
||||
LegDepthConsumer.Create(-2, 0.001, new(40, 0.118), new(42.5, 0.068), new(45, 1.1)),
|
||||
};
|
||||
synthBook = getSyntheticBook(legs);
|
||||
expectedBook = new() {
|
||||
new(10, 0.059),
|
||||
new(5, 0.034),
|
||||
new(0, 0.112),
|
||||
new(-2, 0.16),
|
||||
new(-7, 0.035),
|
||||
};
|
||||
Assert.That(synthBook.SequenceEqual(expectedBook, levelComp));
|
||||
}
|
||||
|
||||
static IEnumerable<DepthLevel> getSyntheticBook(LegDepthConsumer leg1, LegDepthConsumer leg2) {
|
||||
while (!leg1.Depth.IsDone && !leg2.Depth.IsDone) {
|
||||
if (!leg1.LegQuantity.is_real() || !leg2.LegQuantity.is_real()) {
|
||||
throw new ArgumentException("Invalid leg quantities");
|
||||
}
|
||||
double leg2PerLeg1 = (leg2.LegQuantity / leg1.LegQuantity).abs();
|
||||
double leg1PerLeg2 = (leg1.LegQuantity / leg2.LegQuantity).abs();
|
||||
double avail1 = leg1.Depth.AvailableQuantity(1);
|
||||
double avail2 = leg2.Depth.AvailableQuantity(1);
|
||||
(double take1, double take2) = (avail1 / leg1PerLeg2) < (avail2 / leg2PerLeg1)
|
||||
? (avail1, avail1 * leg2PerLeg1)
|
||||
: (avail2 * leg1PerLeg2, avail2);
|
||||
var (qty1, cost1) = leg1.Depth.ConsumeQuantity(take1);
|
||||
var (qty2, cost2) = leg2.Depth.ConsumeQuantity(take2);
|
||||
Log.assert(qty2 / qty1 == leg2PerLeg1);
|
||||
var price1 = cost1 / qty1;
|
||||
var price2 = cost2 / qty2;
|
||||
price1 *= leg1.LegQuantity;
|
||||
price2 *= leg2.LegQuantity;
|
||||
yield return new(Price: price1 + price2, Quantity: qty1);
|
||||
}
|
||||
}
|
||||
|
||||
List<DepthLevel> getSyntheticBook(List<LegDepthConsumer> legs) {
|
||||
if (legs.Count == 0) {
|
||||
return new(0);
|
||||
}
|
||||
var synthLeg = legs.Skip(1).Aggregate(
|
||||
legs[0],
|
||||
(l1, l2) => new LegDepthConsumer(new(getSyntheticBook(l1, l2).ToList())) {
|
||||
LegQuantity = 1,
|
||||
QuantityTick = MathUtils.lcm(l1.QuantityTick, l2.QuantityTick),
|
||||
});
|
||||
return synthLeg.Depth.Levels;
|
||||
}
|
||||
}
|
||||
@@ -254,7 +254,7 @@ class BybitExecutionGateway(ExecutionGateway):
|
||||
self.stop_event.set()
|
||||
|
||||
def create_order(self, req: PyCreateOrderRequest):
|
||||
self.logger.info("[bybit_gw_crt_ord.json]%s" % SLog(req=req))
|
||||
self.logger.info("[bybit_gw_crt_ord.json]%s" % req)
|
||||
if not self.stream_cli or not self.trade_cli or not self.bridge:
|
||||
self._on_abort(
|
||||
req.key(),
|
||||
@@ -287,7 +287,7 @@ class BybitExecutionGateway(ExecutionGateway):
|
||||
self.trade_cli.place_order(lambda msg: self._on_order_ack("crt", msg), **params)
|
||||
|
||||
def cancel_replace_order(self, req: PyCancelReplaceOrderRequest):
|
||||
self.logger.info("[bybit_gw_cxlrep_ord.json]%s" % SLog(req=req))
|
||||
self.logger.info("[bybit_gw_cxlrep_ord.json]%s" % req)
|
||||
if not self.stream_cli or not self.trade_cli:
|
||||
self._on_abort(req.key(), "cxlrep", "WebSocket client not created.")
|
||||
return
|
||||
@@ -313,7 +313,7 @@ class BybitExecutionGateway(ExecutionGateway):
|
||||
)
|
||||
|
||||
def cancel_order(self, req: PyCancelOrderRequest):
|
||||
self.logger.info("[bybit_gw_cxl_ord.json]%s" % SLog(req=req))
|
||||
self.logger.info("[bybit_gw_cxl_ord.json]%s" % req)
|
||||
if not self.stream_cli or not self.trade_cli:
|
||||
self._on_abort(req.key(), "cxl", "WebSocket client not created.")
|
||||
return
|
||||
@@ -333,5 +333,5 @@ class BybitExecutionGateway(ExecutionGateway):
|
||||
)
|
||||
|
||||
def on_securities(self, securities: PySecurityList):
|
||||
self.logger.info("[bybit_gw_sec.json]%s" % SLog(secs=securities))
|
||||
self.logger.info("[bybit_gw_sec.json]%s" % securities)
|
||||
self.securities = {sec.name(): sec for sec in securities}
|
||||
Reference in new issue
Block a user