Skip to content

Commit

Permalink
[MDAPI-67] [.NET] Integrate IndexedEventTxModel
Browse files Browse the repository at this point in the history
  • Loading branch information
Konstantin Ivaschenko committed Jul 25, 2024
1 parent a775236 commit e481f47
Show file tree
Hide file tree
Showing 40 changed files with 3,922 additions and 2 deletions.
4 changes: 3 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ dotnet_diagnostic.CA1861.severity = none
dotnet_diagnostic.CA1846.severity = none
# CA1845 : Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring'
dotnet_diagnostic.CA1845.severity = none
dotnet_diagnostic.cs0693.severity=none
dotnet_diagnostic.CA1000.severity=none
##########################################
# Formatting Rules
# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules
Expand Down Expand Up @@ -361,7 +363,7 @@ dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibi
dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field
dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group
dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style
dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error
dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = none

# Private fields must be camelCase
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md
Expand Down
6 changes: 6 additions & 0 deletions ReleaseNotes.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@

* [MDAPI-93] [.NET] Add DxFeedMarketDepth sample
* [MDAPI-109] [.NET] Add DxFeedCandleChart API sample
* [MDAPI-107] [.NET] Implement TimeSeriesTxModel
* [MDAPI-106] [.NET] Implement IndexedTxModel
* [MDAPI-92] [.NET] Implement MarketDepthModel
* [MDAPI-66] [.NET] Failed to build solution on Visual Studio 2022

## Version 2.2.0
Expand Down
1 change: 1 addition & 0 deletions StyleCop.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
<Rule Id="SA1649" Action="None"/><!-- FileNameMustMatchTypeName -->
<Rule Id="SA1310" Action="None"/><!-- FieldNamesMustNotContainUnderscore -->
<Rule Id="SA1629" Action="None"/><!-- DocumentationTextMustEndWithAPeriod -->
<Rule Id="SA1401" Action="None"/><!-- FieldsMustBePrivate -->
</Rules>
</RuleSet>
21 changes: 21 additions & 0 deletions dxfeed-graal-net-api.sln
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleAuthSample", "samples
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CandleDataResponseReader", "samples\CandleDataResponseReader\CandleDataResponseReader.csproj", "{2567935E-FEFB-470A-BF17-7A883735C4BF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarketDepthModelSample", "samples\MarketDepthModelSample\MarketDepthModelSample.csproj", "{930B1039-B76C-42C5-AD0F-9FA1A1FC9D84}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CandleChartSample", "samples\CandleChartSample\CandleChartSample.csproj", "{B74E8A86-1AB7-4B36-AED3-292CDD95BF90}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IncOrderSnapshotSample", "samples\IncOrderSnapshotSample\IncOrderSnapshotSample.csproj", "{9B758A5C-8AA1-4560-A7CF-EF375B9A1E4C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -112,6 +118,18 @@ Global
{2567935E-FEFB-470A-BF17-7A883735C4BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2567935E-FEFB-470A-BF17-7A883735C4BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2567935E-FEFB-470A-BF17-7A883735C4BF}.Release|Any CPU.Build.0 = Release|Any CPU
{930B1039-B76C-42C5-AD0F-9FA1A1FC9D84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{930B1039-B76C-42C5-AD0F-9FA1A1FC9D84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{930B1039-B76C-42C5-AD0F-9FA1A1FC9D84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{930B1039-B76C-42C5-AD0F-9FA1A1FC9D84}.Release|Any CPU.Build.0 = Release|Any CPU
{B74E8A86-1AB7-4B36-AED3-292CDD95BF90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B74E8A86-1AB7-4B36-AED3-292CDD95BF90}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B74E8A86-1AB7-4B36-AED3-292CDD95BF90}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B74E8A86-1AB7-4B36-AED3-292CDD95BF90}.Release|Any CPU.Build.0 = Release|Any CPU
{9B758A5C-8AA1-4560-A7CF-EF375B9A1E4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B758A5C-8AA1-4560-A7CF-EF375B9A1E4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B758A5C-8AA1-4560-A7CF-EF375B9A1E4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B758A5C-8AA1-4560-A7CF-EF375B9A1E4C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{73597E04-D8A8-4991-A759-7F886CBE2A8F} = {C4490D74-2970-4A1B-8178-A724A06B140A}
Expand All @@ -129,5 +147,8 @@ Global
{C457D53F-A033-465C-B250-A6CC60D02F98} = {C4490D74-2970-4A1B-8178-A724A06B140A}
{B9088D14-10F6-4D88-876D-062B9F6494AB} = {C4490D74-2970-4A1B-8178-A724A06B140A}
{2567935E-FEFB-470A-BF17-7A883735C4BF} = {C4490D74-2970-4A1B-8178-A724A06B140A}
{930B1039-B76C-42C5-AD0F-9FA1A1FC9D84} = {C4490D74-2970-4A1B-8178-A724A06B140A}
{B74E8A86-1AB7-4B36-AED3-292CDD95BF90} = {C4490D74-2970-4A1B-8178-A724A06B140A}
{9B758A5C-8AA1-4560-A7CF-EF375B9A1E4C} = {C4490D74-2970-4A1B-8178-A724A06B140A}
EndGlobalSection
EndGlobal
2 changes: 2 additions & 0 deletions dxfeed-graal-net-api.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=affinitized/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Antici/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ARCX/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=axaml/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=BINY/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=CBSX/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Coeff/@EntryIndexedValue">True</s:Boolean>
Expand Down Expand Up @@ -30,6 +31,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=multiplexor/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dkey/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ddxfeed/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ohlc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=OPASPS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=opol/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Osub/@EntryIndexedValue">True</s:Boolean>
Expand Down
10 changes: 10 additions & 0 deletions samples/CandleChartSample/App.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="CandleChartSample.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->

<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
23 changes: 23 additions & 0 deletions samples/CandleChartSample/App.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;

namespace CandleChartSample;

public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}

public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}

base.OnFrameworkInitializationCompleted();
}
}
26 changes: 26 additions & 0 deletions samples/CandleChartSample/CandleChartSample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFrameworks>net6.0</TargetFrameworks>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.10"/>
<PackageReference Include="Avalonia.Desktop" Version="11.0.10"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.10"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.10"/>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.10"/>
<PackageReference Include="ScottPlot.Avalonia" Version="5.0.36"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\DxFeed.Graal.Net\DxFeed.Graal.Net.csproj"/>
</ItemGroup>

</Project>
57 changes: 57 additions & 0 deletions samples/CandleChartSample/CandleExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// <copyright file="CandleExtension.cs" company="Devexperts LLC">
// Copyright © 2024 Devexperts LLC. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// </copyright>

using System;
using DxFeed.Graal.Net.Events.Candles;
using ScottPlot;

namespace CandleChartSample;

/// <summary>
/// Provides extension methods for converting <see cref="Candle"/> objects to <see cref="OHLC"/> objects.
/// </summary>
public static class CandleExtension
{
/// <summary>
/// Converts a <see cref="Candle"/> object to an <see cref="OHLC"/> object.
/// </summary>
/// <param name="candle">The candle to convert.</param>
/// <returns>An <see cref="OHLC"/> object representing the candle.</returns>
public static OHLC ToOHLC(this Candle candle)
{
var open = GetValueWithPriority(candle.Open, candle.High, candle.Low, candle.Close);
var high = GetValueWithPriority(candle.High, candle.Open, candle.Low, candle.Close);
var low = GetValueWithPriority(candle.Low, candle.Close, candle.Open, candle.High);
var close = GetValueWithPriority(candle.Close, candle.Low, candle.Open, candle.High);

return new OHLC
{
Open = open,
High = high,
Low = low,
Close = close,
DateTime = DateTimeOffset.FromUnixTimeMilliseconds(candle.Time).DateTime.ToLocalTime(),
TimeSpan = TimeSpan.FromMilliseconds(candle.CandleSymbol!.Period!.PeriodIntervalMillis)
};
}

/// <summary>
/// Returns the first non-NaN value from the provided list of values.
/// </summary>
/// <param name="values">An array of double values to check.</param>
/// <returns>The first non-NaN value, or NaN if all values are NaN.</returns>
private static double GetValueWithPriority(params double[] values)
{
foreach (var value in values)
{
if (!double.IsNaN(value))
{
return value;
}
}
return double.NaN;
}
}
177 changes: 177 additions & 0 deletions samples/CandleChartSample/CandleList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// <copyright file="CandleList.cs" company="Devexperts LLC">
// Copyright © 2024 Devexperts LLC. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// </copyright>

using System;
using System.Collections.Generic;
using System.Linq;
using DxFeed.Graal.Net.Events;
using DxFeed.Graal.Net.Events.Candles;
using ScottPlot;

namespace CandleChartSample;

/// <summary>
/// The CandleList class manages a list of OHLC (Open, High, Low, Close) objects for candle chart visualization.
/// This class provides methods for updating the list based on incoming candle events,
/// supporting both snapshot and incremental updates.
/// </summary>
public class CandleList : List<OHLC>
{
/// <summary>
/// Updates the candle list with a new set of candles.
/// Depending on whether the update is a snapshot or incremental, it will replace or update the existing list.
/// </summary>
/// <param name="candles">The collection of candles to update the list with.</param>
/// <param name="isSnapshot">Indicates whether the update is a snapshot (true) or incremental (false).</param>
public void Update(IEnumerable<Candle> candles, bool isSnapshot)
{
// Sort candles by their index. This is stable sort.
var sortedCandles = candles.OrderBy(c => c.Index);

if (isSnapshot)
{
// Handle snapshot update
UpdateSnapshot(sortedCandles);
}
else
{
// Handle incremental update
UpdateIncremental(sortedCandles);
}
}

/// <summary>
/// Updates the list with a snapshot of candles.
/// Clears the existing list and adds the new set of candles, ensuring to remove any that should be removed.
/// </summary>
/// <param name="candles">The snapshot of candles to update the list with.</param>
private void UpdateSnapshot(IEnumerable<Candle> candles)
{
// Clear the current list of OHLC objects
Clear();
foreach (var candle in candles)
{
// Check if the candle should be removed
if (!ShouldRemove(candle))
{
// Convert the candle to OHLC and add to the list
Add(candle.ToOHLC());
}
}
}

/// <summary>
/// Updates the list incrementally with a set of candles.
/// Adds, updates, or removes candles as necessary based on the provided list.
/// </summary>
/// <param name="candles">The incremental set of candles to update the list with.</param>
private void UpdateIncremental(IEnumerable<Candle> candles)
{
foreach (var candle in candles)
{
// Check if the candle should be removed.
if (ShouldRemove(candle))
{
// Remove the corresponding OHLC object.
RemoveCandle(candle);
continue;
}

// Convert the candle to OHLC.
var ohlc = candle.ToOHLC();
// Gets last OHLC.
var lastOhlc = LastOrDefault();

// Compare the date and time of the OHLC object with the last one in the list
switch (DateTime.Compare(ohlc.DateTime, lastOhlc.DateTime))
{
case < 0:
// If the new OHLC is older, insert or update it in the correct position.
InsertOrUpdate(ohlc);
break;
case 0:
// If the new OHLC has the same date and time, update the last one.
AddOrUpdateLast(ohlc);
break;
case > 0:
// If the new OHLC is newer, add it to the list.
Add(ohlc);
break;
}
}
}

/// <summary>
/// Adds a new OHLC object or updates the last one if it exists.
/// </summary>
/// <param name="ohlc">The OHLC object to add or update.</param>
private void AddOrUpdateLast(OHLC ohlc)
{
if (IsEmpty())
{
// Add the OHLC if the list is empty.
Add(ohlc);
}
else
{
// Update the last OHLC object.
this[Count - 1] = ohlc;
}
}

/// <summary>
/// Inserts a new OHLC object into the list or updates an existing one based on its date and time.
/// </summary>
/// <param name="ohlc">The OHLC object to insert or update.</param>
private void InsertOrUpdate(OHLC ohlc)
{
// Find the index of the OHLC object to insert or update.
var index = FindIndex(o => DateTime.Compare(ohlc.DateTime, o.DateTime) <= 0);
if (index >= 0 && this[index].DateTime.Equals(ohlc.DateTime))
{
// Update the existing OHLC object if the date and time match.
this[index] = ohlc;
}
else
{
// Insert the new OHLC object at the correct position.
Insert(index >= 0 ? index : 0, ohlc);
}
}

/// <summary>
/// Removes a candle from the list based on its date and time.
/// </summary>
/// <param name="candle">The candle to remove.</param>
private void RemoveCandle(Candle candle) =>
RemoveAll(ohlc => candle.ToOHLC().DateTime.Equals(ohlc.DateTime));

/// <summary>
/// Checks if the list is empty.
/// </summary>
/// <returns><c>true</c> if the list is empty; otherwise, <c>false</c>.</returns>
private bool IsEmpty() =>
Count == 0;

/// <summary>
/// Returns the last OHLC object in the list or a new OHLC object if the list is empty.
/// </summary>
/// <returns>The last OHLC object or a new OHLC object if the list is empty.</returns>
private OHLC LastOrDefault() =>
IsEmpty() ? new OHLC() : this[Count - 1];

/// <summary>
/// Determines whether a candle should be removed.
/// </summary>
/// <param name="candle">The candle to check.</param>
/// <returns><c>true</c> if the candle should be removed; otherwise, <c>false</c>.</returns>
private static bool ShouldRemove(Candle candle) =>
EventFlags.IsRemove(candle) ||
(double.IsNaN(candle.Open) &&
double.IsNaN(candle.High) &&
double.IsNaN(candle.Low) &&
double.IsNaN(candle.Close));
}
Loading

0 comments on commit e481f47

Please sign in to comment.