Skip to content

Commit 789cc65

Browse files
committed
Samples
1 parent 9521200 commit 789cc65

File tree

7 files changed

+453
-18
lines changed

7 files changed

+453
-18
lines changed

Core/Core.Tests/AverageServiceTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ public void SimpleAverage()
1212
{
1313
var service = new AverageService();
1414

15-
Assert.Equal(0, service.SimpleAverage(items, -5, 5));
16-
Assert.Equal(5, service.SimpleAverage(items, 0, 5));
15+
Assert.Equal(39, service.SimpleAverage(items, -5, 5));
16+
Assert.Equal(39, service.SimpleAverage(items, 0, 5));
1717
Assert.Equal(15, service.SimpleAverage(items, 2, 2));
1818
Assert.Equal(40, service.SimpleAverage(items, 3, 2));
1919
Assert.Equal(246, service.SimpleAverage(items, items.Count - 1, 5));
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@inherits BasePage
2+
3+
@page "/futures/covariance"
4+
5+
<ControlsComponent Adapters="Adapters">
6+
<ChartsComponent Name="Prices" @ref="DataView" />
7+
<ChartsComponent Name="Score" @ref="ScoreView" />
8+
<ChartsComponent Name="Indicators" @ref="IndicatorsView" />
9+
<ChartsComponent Name="Performance" @ref="PerformanceView" />
10+
<OrdersComponent Name="Orders" @ref="OrdersView" />
11+
<PositionsComponent Name="Positions" @ref="PositionsView" />
12+
<TransactionsComponent Name="Transactions" @ref="TransactionsView" />
13+
<StatementsComponent Name="Statements" Adapters="Adapters" @ref="StatementsView" />
14+
</ControlsComponent>
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
using Canvas.Core.Shapes;
2+
using Core.Enums;
3+
using Core.Indicators;
4+
using Core.Models;
5+
using Core.Services;
6+
using Dashboard.Components;
7+
using Simulation;
8+
using SkiaSharp;
9+
using System;
10+
using System.Collections.Generic;
11+
using System.Linq;
12+
using System.Threading.Tasks;
13+
14+
namespace Dashboard.Pages.Futures
15+
{
16+
public partial class Covariance
17+
{
18+
ControlsComponent View { get; set; }
19+
ChartsComponent DataView { get; set; }
20+
ChartsComponent ScoreView { get; set; }
21+
ChartsComponent IndicatorsView { get; set; }
22+
ChartsComponent PerformanceView { get; set; }
23+
TransactionsComponent TransactionsView { get; set; }
24+
OrdersComponent OrdersView { get; set; }
25+
PositionsComponent PositionsView { get; set; }
26+
StatementsComponent StatementsView { get; set; }
27+
PerformanceIndicator Performance { get; set; }
28+
Dictionary<string, ScaleIndicator> Scales { get; set; }
29+
30+
double Deviation { get; set; } = 2;
31+
AverageService AverageService { get; set; } = new();
32+
33+
Dictionary<string, Instrument> Instruments = new()
34+
{
35+
["ESU25"] = new() { Name = "ESU25", StepValue = 12.50, StepSize = 0.25, Leverage = 50, Commission = 3.65, TimeFrame = TimeSpan.FromSeconds(1) },
36+
["NQU25"] = new() { Name = "NQU25", StepValue = 5, StepSize = 0.25, Leverage = 20, Commission = 3.65, TimeFrame = TimeSpan.FromSeconds(1) },
37+
};
38+
39+
protected override async Task OnView()
40+
{
41+
await DataView.Create(nameof(DataView));
42+
await ScoreView.Create(nameof(ScoreView));
43+
await IndicatorsView.Create(nameof(IndicatorsView));
44+
await PerformanceView.Create(nameof(PerformanceView));
45+
46+
DataView.Composers.ForEach(o => o.ShowIndex = i => GetDate(o.Items, (int)i));
47+
ScoreView.Composers.ForEach(o => o.ShowIndex = i => GetDate(o.Items, (int)i));
48+
IndicatorsView.Composers.ForEach(o => o.ShowIndex = i => GetDate(o.Items, (int)i));
49+
PerformanceView.Composers.ForEach(o => o.ShowIndex = i => GetDate(o.Items, (int)i));
50+
}
51+
52+
protected override Task OnTrade()
53+
{
54+
var adapter = Adapter = new SimGateway
55+
{
56+
Connector = Connector,
57+
Source = Configuration["Documents:Resources"] + "/FUTS/2025-06-17",
58+
Account = new()
59+
{
60+
Descriptor = "Demo",
61+
Balance = 25000,
62+
Instruments = Instruments
63+
}
64+
};
65+
66+
Performance = new PerformanceIndicator { Name = "Balance" };
67+
Scales = adapter.Account.Instruments.Keys.ToDictionary(o => o, name => new ScaleIndicator
68+
{
69+
Name = name,
70+
Min = -1,
71+
Max = 1
72+
});
73+
74+
return base.OnTrade();
75+
}
76+
77+
protected override async void OnViewUpdate(Instrument instrument)
78+
{
79+
var adapter = Adapter;
80+
var account = adapter.Account;
81+
var price = instrument.Price;
82+
var index = price.Bar.Time.Value;
83+
var assetX = account.Instruments["ESU25"];
84+
var assetY = account.Instruments["NQU25"];
85+
var seriesX = (await adapter.GetPriceGroups(new Criteria { Count = 100, Instrument = assetX })).Data;
86+
var seriesY = (await adapter.GetPriceGroups(new Criteria { Count = 100, Instrument = assetY })).Data;
87+
88+
if (seriesX.Count is 0 || seriesY.Count is 0)
89+
{
90+
return;
91+
}
92+
93+
var performance = await Performance.Update([adapter]);
94+
var scaleX = await Scales[assetX.Name].Update(seriesX);
95+
var scaleY = await Scales[assetY.Name].Update(seriesY);
96+
var priceX = seriesX.Last();
97+
var priceY = seriesY.Last();
98+
var retSeriesX = seriesX.Select(o => o.Last.Value * assetX.Leverage.Value).ToArray();
99+
var retSeriesY = seriesY.Select(o => o.Last.Value * assetY.Leverage.Value).ToArray();
100+
var beta = CalculateHedgeRatio(retSeriesX, retSeriesY);
101+
var score = CalculateZScore(retSeriesX, retSeriesY, beta, (priceX.Last * assetX.Leverage.Value - beta * priceY.Last * assetY.Leverage.Value).Value);
102+
103+
OrdersView.Update(Adapters.Values);
104+
PositionsView.Update(Adapters.Values);
105+
TransactionsView.Update(Adapters.Values);
106+
ScoreView.Update(index, nameof(ScoreView), "Score", new AreaShape { Y = score, Component = ComUp });
107+
DataView.Update(index, nameof(DataView), "Leader", new AreaShape { Y = priceX.Last, Component = ComUp });
108+
IndicatorsView.Update(index, nameof(IndicatorsView), "X", new LineShape { Y = scaleX.Response.Last, Component = ComUp });
109+
IndicatorsView.Update(index, nameof(IndicatorsView), "Y", new LineShape { Y = scaleY.Response.Last, Component = ComDown });
110+
PerformanceView.Update(index, nameof(PerformanceView), "Balance", new AreaShape { Y = account.Balance + account.Performance });
111+
PerformanceView.Update(index, nameof(PerformanceView), "PnL", PerformanceView.GetShape<LineShape>(performance.Response, SKColors.OrangeRed));
112+
}
113+
114+
protected override async Task OnTradeUpdate(Instrument instrument)
115+
{
116+
if (Equals(instrument.Name, "ESU25") is false)
117+
{
118+
return;
119+
}
120+
121+
var price = instrument.Price;
122+
var adapter = Adapter;
123+
var account = adapter.Account;
124+
var assetX = account.Instruments["ESU25"];
125+
var assetY = account.Instruments["NQU25"];
126+
var seriesX = (await adapter.GetPrices(new Criteria { Count = 100, Instrument = assetX })).Data;
127+
var seriesY = (await adapter.GetPrices(new Criteria { Count = 100, Instrument = assetY })).Data;
128+
129+
if (seriesX.Count is 0 || seriesY.Count is 0)
130+
{
131+
return;
132+
}
133+
134+
var orders = (await adapter.GetOrders(default)).Data;
135+
var positions = (await adapter.GetPositions(default)).Data;
136+
var priceX = seriesX.Last();
137+
var priceY = seriesY.Last();
138+
139+
if (orders.Count is 0)
140+
{
141+
var retSeriesX = seriesX.Select(o => o.Last.Value * assetX.Leverage.Value).ToArray();
142+
var retSeriesY = seriesY.Select(o => o.Last.Value * assetY.Leverage.Value).ToArray();
143+
var beta = CalculateHedgeRatio(retSeriesX, retSeriesY);
144+
var score = CalculateZScore(retSeriesX, retSeriesY, beta, (priceX.Last * assetX.Leverage.Value - beta * priceY.Last * assetY.Leverage.Value).Value);
145+
var isLong = score < -Deviation;
146+
var isShort = score > Deviation;
147+
148+
if (Equals(price.Bar.Time, scores.LastOrDefault().Item1))
149+
{
150+
scores[scores.Count - 1] = (price.Bar.Time.Value, score);
151+
}
152+
else
153+
{
154+
scores.Add((price.Bar.Time.Value, score));
155+
}
156+
157+
var prevScore = scores.ElementAtOrDefault(scores.Count - 2).Item2;
158+
159+
if (positions.Count is 0)
160+
{
161+
switch (true)
162+
{
163+
case true when isLong:
164+
await OpenPosition(adapter, assetX, OrderSideEnum.Long);
165+
await OpenPosition(adapter, assetY, OrderSideEnum.Short);
166+
break;
167+
168+
case true when isShort:
169+
await OpenPosition(adapter, assetX, OrderSideEnum.Short);
170+
await OpenPosition(adapter, assetY, OrderSideEnum.Long);
171+
break;
172+
}
173+
}
174+
175+
if (positions.Count is not 0)
176+
{
177+
var pos = positions.First();
178+
var closeLong = pos.Side is OrderSideEnum.Long && prevScore > 0;
179+
var closeShort = pos.Side is OrderSideEnum.Short && prevScore < 0;
180+
181+
if (closeLong || closeShort)
182+
{
183+
await ClosePosition(adapter);
184+
}
185+
}
186+
}
187+
}
188+
189+
List<(long, double)> scores = new();
190+
191+
/// <summary>
192+
/// Calculates the Hedge Ratio (Beta) between two time series (Y vs X) using simple linear regression.
193+
/// This should be calculated over a long look-back window (e.g., 252 daily bars).
194+
/// </summary>
195+
/// <param name="y">The time series of the dependent asset (Asset 1, the one being hedged).</param>
196+
/// <param name="x">The time series of the independent asset (Asset 2, the hedging instrument).</param>
197+
/// <returns>The Hedge Ratio (Beta) - the number of units of X required to hedge 1 unit of Y.</returns>
198+
public static double CalculateHedgeRatio(double[] y, double[] x)
199+
{
200+
if (y == null || x == null || y.Length != x.Length || y.Length < 2)
201+
{
202+
return 0;
203+
}
204+
205+
int n = y.Length;
206+
207+
// 1. Calculate Averages
208+
double avgY = y.Average();
209+
double avgX = x.Average();
210+
211+
// 2. Calculate Numerator (Covariance equivalent: Sum of (xi - avgX) * (yi - avgY))
212+
double numerator = 0;
213+
for (int i = 0; i < n; i++)
214+
{
215+
numerator += (x[i] - avgX) * (y[i] - avgY);
216+
}
217+
218+
// 3. Calculate Denominator (Variance equivalent: Sum of (xi - avgX)^2)
219+
double denominator = 0;
220+
for (int i = 0; i < n; i++)
221+
{
222+
denominator += Math.Pow(x[i] - avgX, 2);
223+
}
224+
225+
// 4. Calculate Beta (Hedge Ratio) = Covariance(X, Y) / Variance(X)
226+
if (denominator == 0)
227+
{
228+
// Avoid division by zero, which implies no variation in the hedging asset price.
229+
return 0.0;
230+
}
231+
232+
return numerator / denominator;
233+
}
234+
235+
/// <summary>
236+
/// Calculates the Z-Score for the current spread relative to its historical mean and standard deviation.
237+
/// This should be calculated over a shorter, rolling look-back window (e.g., 60-90 bars)
238+
/// using the spread history that was already calculated using the Hedge Ratio.
239+
/// </summary>
240+
/// <param name="spreadHistory">Historical values of the calculated spread (Spread = Y - Beta * X).</param>
241+
/// <param name="currentSpread">The current calculated value of the spread.</param>
242+
/// <returns>The Z-score: the number of standard deviations the current spread is from the mean.</returns>
243+
public static double CalculateZScore(double[] yHistory, double[] xHistory, double beta, double currentSpread)
244+
{
245+
if (yHistory == null || xHistory == null || yHistory.Length != xHistory.Length || yHistory.Length < 2)
246+
{
247+
return 0;
248+
}
249+
250+
// 1. Calculate the historical spread internally: Spread = Y - Beta * X
251+
List<double> spreadHistory = new List<double>();
252+
for (int i = 0; i < yHistory.Length; i++)
253+
{
254+
spreadHistory.Add(yHistory[i] - beta * xHistory[i]);
255+
}
256+
257+
// Now, proceed with mean and standard deviation calculation based on the generated spreadHistory
258+
int n = spreadHistory.Count;
259+
260+
// 1. Calculate the Mean (Average) of the historical spread
261+
double mean = spreadHistory.Average();
262+
263+
// 2. Calculate the Standard Deviation of the historical spread
264+
double sumOfSquares = 0;
265+
266+
foreach (var spread in spreadHistory)
267+
{
268+
sumOfSquares += Math.Pow(spread - mean, 2);
269+
}
270+
271+
// Use N-1 for sample standard deviation, though N is also common in finance.
272+
double variance = sumOfSquares / (n - 1);
273+
double standardDeviation = Math.Sqrt(variance);
274+
275+
// 3. Calculate the Z-Score
276+
if (standardDeviation == 0)
277+
{
278+
// Spread is perfectly flat, Z-score is undefined or 0.
279+
return 0.0;
280+
}
281+
282+
return (currentSpread - mean) / standardDeviation;
283+
}
284+
}
285+
}

Dashboard/Dashboard/Pages/Futures/Leads.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
@page "/futures/leads"
44

55
<ControlsComponent Adapters="Adapters">
6-
<ChartsComponent Name="Prices" @ref="LeaderView" />
7-
<ChartsComponent Name="Spread" @ref="FollowerView" />
6+
<ChartsComponent Name="Prices" @ref="DataView" />
7+
<ChartsComponent Name="Spread" @ref="SpreadView" />
88
<ChartsComponent Name="Indicators" @ref="IndicatorsView" />
99
<ChartsComponent Name="Performance" @ref="PerformanceView" />
1010
<OrdersComponent Name="Orders" @ref="OrdersView" />

Dashboard/Dashboard/Pages/Futures/Leads.razor.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ namespace Dashboard.Pages.Futures
1515
public partial class Leads
1616
{
1717
ControlsComponent View { get; set; }
18-
ChartsComponent LeaderView { get; set; }
19-
ChartsComponent FollowerView { get; set; }
18+
ChartsComponent DataView { get; set; }
19+
ChartsComponent SpreadView { get; set; }
2020
ChartsComponent IndicatorsView { get; set; }
2121
ChartsComponent PerformanceView { get; set; }
2222
TransactionsComponent TransactionsView { get; set; }
@@ -37,13 +37,13 @@ public partial class Leads
3737

3838
protected override async Task OnView()
3939
{
40-
await LeaderView.Create("Prices");
41-
await FollowerView.Create("Prices");
42-
await IndicatorsView.Create("Indicators");
43-
await PerformanceView.Create("Performance");
40+
await DataView.Create(nameof(DataView));
41+
await SpreadView.Create(nameof(SpreadView));
42+
await IndicatorsView.Create(nameof(IndicatorsView));
43+
await PerformanceView.Create(nameof(PerformanceView));
4444

45-
LeaderView.Composers.ForEach(o => o.ShowIndex = i => GetDate(o.Items, (int)i));
46-
FollowerView.Composers.ForEach(o => o.ShowIndex = i => GetDate(o.Items, (int)i));
45+
DataView.Composers.ForEach(o => o.ShowIndex = i => GetDate(o.Items, (int)i));
46+
SpreadView.Composers.ForEach(o => o.ShowIndex = i => GetDate(o.Items, (int)i));
4747
IndicatorsView.Composers.ForEach(o => o.ShowIndex = i => GetDate(o.Items, (int)i));
4848
PerformanceView.Composers.ForEach(o => o.ShowIndex = i => GetDate(o.Items, (int)i));
4949
}
@@ -98,12 +98,12 @@ protected override async void OnViewUpdate(Instrument instrument)
9898
OrdersView.Update(Adapters.Values);
9999
PositionsView.Update(Adapters.Values);
100100
TransactionsView.Update(Adapters.Values);
101-
LeaderView.Update(price.Time.Value, "Prices", "Leader", new AreaShape { Y = priceX.Last, Component = ComUp });
102-
FollowerView.Update(price.Time.Value, "Prices", "Spread", new AreaShape { Y = spread, Component = Com });
103-
IndicatorsView.Update(price.Time.Value, "Indicators", "X", new LineShape { Y = scaleX.Response.Last, Component = ComUp });
104-
IndicatorsView.Update(price.Time.Value, "Indicators", "Y", new LineShape { Y = scaleY.Response.Last, Component = ComDown });
105-
PerformanceView.Update(price.Time.Value, "Performance", "Balance", new AreaShape { Y = account.Balance + account.Performance });
106-
PerformanceView.Update(price.Time.Value, "Performance", "PnL", PerformanceView.GetShape<LineShape>(performance.Response, SKColors.OrangeRed));
101+
DataView.Update(price.Time.Value, nameof(DataView), "Leader", new AreaShape { Y = priceX.Last, Component = ComUp });
102+
SpreadView.Update(price.Time.Value, nameof(SpreadView), "Spread", new AreaShape { Y = spread, Component = Com });
103+
IndicatorsView.Update(price.Time.Value, nameof(IndicatorsView), "X", new LineShape { Y = scaleX.Response.Last, Component = ComUp });
104+
IndicatorsView.Update(price.Time.Value, nameof(IndicatorsView), "Y", new LineShape { Y = scaleY.Response.Last, Component = ComDown });
105+
PerformanceView.Update(price.Time.Value, nameof(PerformanceView), "Balance", new AreaShape { Y = account.Balance + account.Performance });
106+
PerformanceView.Update(price.Time.Value, nameof(PerformanceView), "PnL", PerformanceView.GetShape<LineShape>(performance.Response, SKColors.OrangeRed));
107107
}
108108

109109
protected override async Task OnTradeUpdate(Instrument instrument)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@inherits BasePage
2+
3+
@page "/shares/convex-fade"
4+
5+
<ControlsComponent Adapters="Adapters">
6+
<ChartsComponent Name="Indicators" @ref="DataView" />
7+
<ChartsComponent Name="Performance" @ref="PerformanceView" />
8+
<OrdersComponent Name="Orders" @ref="OrdersView" />
9+
<PositionsComponent Name="Positions" @ref="PositionsView" />
10+
<TransactionsComponent Name="Transactions" @ref="TransactionsView" />
11+
<StatementsComponent Name="Statements" Adapters="Adapters" @ref="StatementsView" />
12+
</ControlsComponent>

0 commit comments

Comments
 (0)