Skip to content

Commit

Permalink
[MDAPI-22] [.NET] CandleWebService API sample
Browse files Browse the repository at this point in the history
  • Loading branch information
Konstantin Ivaschenko committed Jun 28, 2024
1 parent d0c3f90 commit 77735c8
Show file tree
Hide file tree
Showing 9 changed files with 427 additions and 15 deletions.
30 changes: 16 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,16 @@ ready to answer any questions and help with the transition.

#### Sample Mapping

| # | Sample | Old Version | New Version |
|:-:|:----------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------|
| 1 | How to get Instrument Profiles | [dxf_ipf_connect_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_ipf_connect_sample) | [DxFeedIpfConnect](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/DxFeedIpfConnect) |
| 2 | How to get live updates for Instrument Profiles | [dxf_instrument_profile_live_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_instrument_profile_live_sample) | [DxFeedLiveIpfSample](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/DxFeedLiveIpfSample) |
| 3 | How to subscribe to `Order`, `SpreadOrder`, `Candle`, `TimeAndSale`, `Greeks`, `Series` snapshots | [dxf_snapshot_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_snapshot_sample) | *Q2’2024*, please see [TBD](#future-development) section |
| 4 | How to subscribe to depth of market | [dxf_price_level_book_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_price_level_book_sample) | *Q2’2024*, please see [TBD](#future-development) section |
| 5 | How to receive snapshots of `TimeAndSale`, `Candle`, `Series`, `Greeks` events on a given time interval without live subscription | [dxf_simple_data_retrieving_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_simple_data_retrieving_sample) | [FetchDailyCandles](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/FetchDailyCandles) |
| 6 | How to subscribe to order snapshot with incremental updates | [dxf_inc_order_snapshot_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_inc_order_snapshot_sample) | *Q2’2024*, please see [TBD](#future-development) section |
| 7 | How to retrieve `Candle` data from the candle web service | [dxf_candle_data_retrieving_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_candle_data_retrieving_sample) | *Q4’2024*, please see [TBD](#future-development) section |
| 8 | How to retrieve `TimeAndSale` data from the candle web service | [dxf_tns_data_retrieving_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_tns_data_retrieving_sample) | *Q4’2024*, please see [TBD](#future-development) section |
| # | Sample | Old Version | New Version |
|:-:|:----------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------|
| 1 | How to get Instrument Profiles | [dxf_ipf_connect_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_ipf_connect_sample) | [DxFeedIpfConnect](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/DxFeedIpfConnect) |
| 2 | How to get live updates for Instrument Profiles | [dxf_instrument_profile_live_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_instrument_profile_live_sample) | [DxFeedLiveIpfSample](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/DxFeedLiveIpfSample) |
| 3 | How to subscribe to `Order`, `SpreadOrder`, `Candle`, `TimeAndSale`, `Greeks`, `Series` snapshots | [dxf_snapshot_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_snapshot_sample) | *Q2’2024*, please see [TBD](#future-development) section |
| 4 | How to subscribe to depth of market | [dxf_price_level_book_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_price_level_book_sample) | *Q2’2024*, please see [TBD](#future-development) section |
| 5 | How to receive snapshots of `TimeAndSale`, `Candle`, `Series`, `Greeks` events on a given time interval without live subscription | [dxf_simple_data_retrieving_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_simple_data_retrieving_sample) | [FetchDailyCandles](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/FetchDailyCandles) |
| 6 | How to subscribe to order snapshot with incremental updates | [dxf_inc_order_snapshot_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_inc_order_snapshot_sample) | *Q2’2024*, please see [TBD](#future-development) section |
| 7 | How to retrieve `Candle` data from the candle web service | [dxf_candle_data_retrieving_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_candle_data_retrieving_sample) | [CandleDataResponseReader](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/CandleDataResponseReader) | |
| 8 | How to retrieve `TimeAndSale` data from the candle web service | [dxf_tns_data_retrieving_sample](https://github.com/dxFeed/dxfeed-net-api/tree/master/samples/dxf_tns_data_retrieving_sample) | [CandleDataResponseReader](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/CandleDataResponseReader) | |

### Implementation Details

Expand Down Expand Up @@ -391,11 +391,13 @@ sudo /usr/bin/xattr -r -d com.apple.quarantine <directory_with_tools>
simple demonstration of how to get live updates for Instrument Profiles
- [x] [ScheduleSample](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/ScheduleSample) is a simple
demonstration of how to get various scheduling information for instruments
- [x] [FetchDailyCandles](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/FetchDailyCandles)
is a simple demonstration of how to fetch last N-days of candles for a specified symbol
- [x] [DxFeedReconnectSample](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/DxFeedReconnectSample)
is a simple demonstration of how to connect to an endpoint, subscribe to market data events, handle reconnections
- [x] [FetchDailyCandles](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/FetchDailyCandles)
is a simple demonstration of how to fetch last N-days of candles for a specified symbol
- [x] [DxFeedReconnectSample](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/DxFeedReconnectSample)
is a simple demonstration of how to connect to an endpoint, subscribe to market data events, handle reconnections
and re-subscribing.
- [x] [CandleDataResponseReader](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/CandleDataResponseReader)
is a simple demonstration of how to parse response from CandleData service.

## Current State

Expand Down
2 changes: 2 additions & 0 deletions ReleaseNotes.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
* [MDAPI-22] [.NET] CandleWebService API sample

## Version 2.1.0

* [MDAPI-21] [Java] [SDK] NoSuchMethodError exception when creating a monitoring endpoint
Expand Down
7 changes: 7 additions & 0 deletions dxfeed-graal-net-api.sln
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DxFeedReconnectSample", "sa
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleAuthSample", "samples\SimpleAuthSample\SimpleAuthSample.csproj", "{B9088D14-10F6-4D88-876D-062B9F6494AB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CandleDataResponseReader", "samples\CandleDataResponseReader\CandleDataResponseReader.csproj", "{2567935E-FEFB-470A-BF17-7A883735C4BF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -106,6 +108,10 @@ Global
{B9088D14-10F6-4D88-876D-062B9F6494AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9088D14-10F6-4D88-876D-062B9F6494AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9088D14-10F6-4D88-876D-062B9F6494AB}.Release|Any CPU.Build.0 = Release|Any CPU
{2567935E-FEFB-470A-BF17-7A883735C4BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{73597E04-D8A8-4991-A759-7F886CBE2A8F} = {C4490D74-2970-4A1B-8178-A724A06B140A}
Expand All @@ -122,5 +128,6 @@ Global
{119E7BB3-4B97-4824-9CA7-0D4C9B57590D} = {C4490D74-2970-4A1B-8178-A724A06B140A}
{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}
EndGlobalSection
EndGlobal
153 changes: 153 additions & 0 deletions samples/CandleDataResponseReader/AbstractTimeSeriesMap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// <copyright file="TimeSeriesMap.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.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using CsvHelper;
using CsvHelper.Configuration;
using CsvHelper.TypeConversion;
using DxFeed.Graal.Net.Events;
using DxFeed.Graal.Net.Utils;

namespace DxFeed.Graal.Net.Samples;

/// <summary>
/// Abstract base class for mapping <see cref="ITimeSeriesEvent"/> to CSV using <see cref="CsvHelper"/>.
/// Provides common functionality for parsing event flags and custom type converters.
/// </summary>
/// <typeparam name="T">The type of time series event to map.</typeparam>
internal abstract class AbstractTimeSeriesMap<T> : ClassMap<T>
where T : ITimeSeriesEvent
{
/// <summary>
/// Parses the <see cref="IIndexedEvent.EventFlags"/> field from the last column in the CSV row.
/// This field may not be represented, and is not contained in the CSV header.
/// </summary>
/// <param name="row">The CSV row being processed.</param>
/// <returns>An integer representing the combined <see cref="IIndexedEvent.EventFlags"/>.</returns>
protected static int ParseEventFlags(IReaderRow row)
{
var text = row.Context.Parser?.Record?.Last();
if (string.IsNullOrEmpty(text) || !text.Contains("EventFlags="))
{
return 0;
}

var i = text.IndexOf("=", StringComparison.Ordinal);
var textFlags = text.Substring(i + 1).Split(',');
var flags = 0;
foreach (var textFlag in textFlags)
{
switch (textFlag)
{
case "TX_PENDING":
flags |= EventFlags.TxPending;
break;
case "REMOVE_EVENT":
flags |= EventFlags.RemoveEvent;
break;
case "SNAPSHOT_BEGIN":
flags |= EventFlags.SnapshotBegin;
break;
case "SNAPSHOT_END":
flags |= EventFlags.SnapshotEnd;
break;
case "SNAPSHOT_SNIP":
flags |= EventFlags.SnapshotSnip;
break;
}
}

return flags;
}

/// <summary>
/// Custom type converter to handle string fields, converting "\NULL" to null.
/// </summary>
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Created by CsvHelper")]
protected class StringConverter : DefaultTypeConverter
{
/// <summary>
/// Converts a string field to its corresponding object representation.
/// </summary>
/// <param name="text">The text to convert.</param>
/// <param name="row">The CSV row being processed.</param>
/// <param name="memberMapData">The member map data for the field.</param>
/// <returns>The converted object, or null if the text is "\NULL".</returns>
public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) =>
text == null || text.Equals("\\NULL", StringComparison.Ordinal) ? null : text;
}

/// <summary>
/// Custom type converter to handle event time fields, converting them to Unix time in milliseconds.
/// </summary>
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Created by CsvHelper")]
protected class EventTimeConverter : DefaultTypeConverter
{
/// <summary>
/// Converts a string representation of event time to Unix time in milliseconds.
/// </summary>
/// <param name="text">The text to convert.</param>
/// <param name="row">The CSV row being processed.</param>
/// <param name="memberMapData">The member map data for the field.</param>
/// <returns>The Unix time in milliseconds.</returns>
public override object ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) =>
string.IsNullOrEmpty(text) ? 0 : TimeFormat.Default.Parse(text).ToUnixTimeMilliseconds();
}

/// <summary>
/// Custom type converter to handle fields representing both time and sequence.
/// </summary>
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Created by CsvHelper")]
protected class TimeAndSequenceConverter : DefaultTypeConverter
{
/// <summary>
/// Converts a string representation of time and sequence to a combined Index value.
/// </summary>
/// <param name="text">The text to convert.</param>
/// <param name="row">The CSV row being processed.</param>
/// <param name="memberMapData">The member map data for the field.</param>
/// <returns>The combined Index value of time and sequence.</returns>
public override object ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) =>
(ParseTime(text) << 32) | (ParseSequence(row.GetField("Sequence")) & 0xFFFFFFFFL);

/// <summary>
/// Parses the time component from the string representation.
/// </summary>
/// <param name="time">The time string to parse.</param>
/// <returns>The parsed time as a long value representing Unix time in seconds.</returns>
private static long ParseTime(string? time)
{
if (string.IsNullOrEmpty(time) || time.Equals("0", StringComparison.Ordinal))
{
return 0;
}

return TimeFormat.Default.Parse(time).ToUnixTimeSeconds();
}

/// <summary>
/// Parses the sequence and millis component from the string representation.
/// </summary>
/// <param name="sequence">The sequence string to parse.</param>
/// <returns>The parsed sequence and millis as an integer value.</returns>
private static int ParseSequence(string? sequence)
{
if (string.IsNullOrEmpty(sequence) || sequence.Equals("0", StringComparison.Ordinal))
{
return 0;
}

var i = sequence.IndexOf(':');
return i >= 0
? (int.Parse(sequence.Substring(0, i), CultureInfo.InvariantCulture) << 22) +
int.Parse(sequence.Substring(i + 1), CultureInfo.InvariantCulture)
: int.Parse(sequence, CultureInfo.InvariantCulture);
}
}
}
25 changes: 25 additions & 0 deletions samples/CandleDataResponseReader/CandleDataResponseReader.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>DxFeed.Graal.Net.Samples</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>../../artifacts/Debug/Samples/</OutputPath>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>../../artifacts/Release/Samples/</OutputPath>
</PropertyGroup>

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

<ItemGroup>
<PackageReference Include="CsvHelper" Version="33.0.1" />
</ItemGroup>

</Project>
Loading

0 comments on commit 77735c8

Please sign in to comment.