From 0097154b74dc0d39b854bf52c181ebbb89f318a8 Mon Sep 17 00:00:00 2001 From: Konstantin Ivaschenko Date: Thu, 27 Jun 2024 21:04:32 +0300 Subject: [PATCH] [MDAPI-22] [.NET] CandleWebService API sample --- README.md | 30 ++-- ReleaseNotes.txt | 2 + dxfeed-graal-net-api.sln | 7 + .../AbstractTimeSeriesMap.cs | 153 +++++++++++++++++ .../CandleDataResponseReader.csproj | 25 +++ samples/CandleDataResponseReader/CandleMap.cs | 34 ++++ samples/CandleDataResponseReader/Program.cs | 154 ++++++++++++++++++ .../TimeAndSaleMap.cs | 37 +++++ .../Events/Market/TimeAndSale.cs | 2 +- 9 files changed, 429 insertions(+), 15 deletions(-) create mode 100644 samples/CandleDataResponseReader/AbstractTimeSeriesMap.cs create mode 100644 samples/CandleDataResponseReader/CandleDataResponseReader.csproj create mode 100644 samples/CandleDataResponseReader/CandleMap.cs create mode 100644 samples/CandleDataResponseReader/Program.cs create mode 100644 samples/CandleDataResponseReader/TimeAndSaleMap.cs diff --git a/README.md b/README.md index 654d3b82..056ff3cc 100644 --- a/README.md +++ b/README.md @@ -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 @@ -391,11 +391,13 @@ sudo /usr/bin/xattr -r -d com.apple.quarantine 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 diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index dd46c483..378d7343 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -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 diff --git a/dxfeed-graal-net-api.sln b/dxfeed-graal-net-api.sln index c3404f61..81acbc49 100644 --- a/dxfeed-graal-net-api.sln +++ b/dxfeed-graal-net-api.sln @@ -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 @@ -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} @@ -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 diff --git a/samples/CandleDataResponseReader/AbstractTimeSeriesMap.cs b/samples/CandleDataResponseReader/AbstractTimeSeriesMap.cs new file mode 100644 index 00000000..de8c18f6 --- /dev/null +++ b/samples/CandleDataResponseReader/AbstractTimeSeriesMap.cs @@ -0,0 +1,153 @@ +// +// 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/. +// + +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; + +/// +/// Abstract base class for mapping to CSV using . +/// Provides common functionality for parsing event flags and custom type converters. +/// +/// The type of time series event to map. +internal abstract class AbstractTimeSeriesMap : ClassMap + where T : ITimeSeriesEvent +{ + /// + /// Parses the field from the last column in the CSV row. + /// This field may not be represented, and is not contained in the CSV header. + /// + /// The CSV row being processed. + /// An integer representing the combined . + 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; + } + + /// + /// Custom type converter to handle string fields, converting "\NULL" to null. + /// + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Created by CsvHelper")] + protected class StringConverter : DefaultTypeConverter + { + /// + /// Converts a string field to its corresponding object representation. + /// + /// The text to convert. + /// The CSV row being processed. + /// The member map data for the field. + /// The converted object, or null if the text is "\NULL". + public override object? ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) => + text == null || text.Equals("\\NULL", StringComparison.Ordinal) ? null : text; + } + + /// + /// Custom type converter to handle event time fields, converting them to Unix time in milliseconds. + /// + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Created by CsvHelper")] + protected class EventTimeConverter : DefaultTypeConverter + { + /// + /// Converts a string representation of event time to Unix time in milliseconds. + /// + /// The text to convert. + /// The CSV row being processed. + /// The member map data for the field. + /// The Unix time in milliseconds. + public override object ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) => + string.IsNullOrEmpty(text) ? 0 : TimeFormat.Default.Parse(text).ToUnixTimeMilliseconds(); + } + + /// + /// Custom type converter to handle fields representing both time and sequence. + /// + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Created by CsvHelper")] + protected class TimeAndSequenceConverter : DefaultTypeConverter + { + /// + /// Converts a string representation of time and sequence to a combined Index value. + /// + /// The text to convert. + /// The CSV row being processed. + /// The member map data for the field. + /// The combined Index value of time and sequence. + public override object ConvertFromString(string? text, IReaderRow row, MemberMapData memberMapData) => + (ParseTime(text) << 32) | (ParseSequence(row.GetField("Sequence")) & 0xFFFFFFFFL); + + /// + /// Parses the time component from the string representation. + /// + /// The time string to parse. + /// The parsed time as a long value representing Unix time in seconds. + private static long ParseTime(string? time) + { + if (string.IsNullOrEmpty(time) || time.Equals("0", StringComparison.Ordinal)) + { + return 0; + } + + return TimeFormat.Default.Parse(time).ToUnixTimeSeconds(); + } + + /// + /// Parses the sequence and millis component from the string representation. + /// + /// The sequence string to parse. + /// The parsed sequence and millis as an integer value. + 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); + } + } +} diff --git a/samples/CandleDataResponseReader/CandleDataResponseReader.csproj b/samples/CandleDataResponseReader/CandleDataResponseReader.csproj new file mode 100644 index 00000000..c87ff149 --- /dev/null +++ b/samples/CandleDataResponseReader/CandleDataResponseReader.csproj @@ -0,0 +1,25 @@ + + + + Exe + DxFeed.Graal.Net.Samples + net6.0 + + + + ../../artifacts/Debug/Samples/ + + + + ../../artifacts/Release/Samples/ + + + + + + + + + + + diff --git a/samples/CandleDataResponseReader/CandleMap.cs b/samples/CandleDataResponseReader/CandleMap.cs new file mode 100644 index 00000000..7aafa3a8 --- /dev/null +++ b/samples/CandleDataResponseReader/CandleMap.cs @@ -0,0 +1,34 @@ +// +// 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/. +// + +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using CsvHelper.Configuration; +using DxFeed.Graal.Net.Events.Candles; + +namespace DxFeed.Graal.Net.Samples; + +/// +/// A class map for the class, used to map CSV data to objects. +/// +[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Created by CsvHelper")] +internal sealed class CandleMap : AbstractTimeSeriesMap +{ + /// + /// Initializes a new instance of the class. + /// This constructor configures the mapping rules for the class. + /// + public CandleMap() + { + // Automatically map all fields using the given CSV configuration. + AutoMap(new CsvConfiguration(CultureInfo.InvariantCulture) { IgnoreReferences = true }); + Map(m => m.EventTime).TypeConverter(); + Map(m => m.Index).Name("Time").TypeConverter(); + Map(m => m.Time).Ignore(); // Time is processed in Index using TimeAndSequenceConverter. + Map(m => m.Sequence).Ignore(); // Sequence is processed in Index using TimeAndSequenceConverter. + Map(m => m.EventFlags).Optional().Convert(args => ParseEventFlags(args.Row)); + } +} diff --git a/samples/CandleDataResponseReader/Program.cs b/samples/CandleDataResponseReader/Program.cs new file mode 100644 index 00000000..3153ed04 --- /dev/null +++ b/samples/CandleDataResponseReader/Program.cs @@ -0,0 +1,154 @@ +// +// Copyright © 2022 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/. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using CsvHelper; +using CsvHelper.Configuration; +using DxFeed.Graal.Net.Auth; +using DxFeed.Graal.Net.Events; +using DxFeed.Graal.Net.Events.Candles; +using DxFeed.Graal.Net.Events.Market; +using static System.Globalization.CultureInfo; + +namespace DxFeed.Graal.Net.Samples; + +/// +/// Demonstrates how to parse response from CandleData service. +/// Candlewebservice provides Candle and TimeAndSale data for particular time period +/// in the past with from-to period via REST-like API. +/// For more details see KB. +/// +internal abstract class Program +{ + private static async Task Main(string[] args) + { + // Create an authentication token. It can be Basic or Bearer authorization. + var token = AuthToken.CreateBasicToken("user", "password"); + // Create an HTTP client with the base URL and the authentication token + using var client = CreateHttpClient("https://tools.dxfeed.com/", token); + + + var start = DateTimeOffset.Now.AddDays(-2).ToString("yyyyMMdd", InvariantCulture); + var stop = DateTimeOffset.Now.AddDays(-1).ToString("yyyyMMdd", InvariantCulture); + // URL for fetching candle events. + var candleUrl = $"candledata?records=Candle&symbols=IBM{{=h}}&start={start}&stop={stop}&format=csv&compression=gzip"; + var response = await client.GetAsync(candleUrl); + // Ensure the HTTP response status is successful. + response.EnsureSuccessStatusCode(); + // Parse the response content into a list of Candle events. + var candles = ParseEvents(response); + Console.WriteLine($"Received candles count: {candles.Count}"); + + start = DateTimeOffset.Now.AddDays(-1).AddHours(-1).ToString("yyyyMMdd-hhmmss", InvariantCulture); + stop = DateTimeOffset.Now.AddDays(-1).ToString("yyyyMMdd-hhmmss", InvariantCulture); + // URL for fetching tns events. + var tnsUrl = $"candledata?records=TimeAndSale&symbols=IBM&start={start}&stop={stop}&format=csv&compression=gzip"; + response = await client.GetAsync(tnsUrl); + // Ensure the HTTP response status is successful. + response.EnsureSuccessStatusCode(); + // Parse the response content into a list of TimeAndSale events. + var timeAndSales = ParseEvents(response); + Console.WriteLine($"Received tns count: {timeAndSales.Count}"); + } + + /// + /// Creates an configured with the base URL and authorization token. + /// + /// The base URL for the API requests. + /// The authorization token. + /// A configured instance. + private static HttpClient CreateHttpClient(string baseUrl, AuthToken? token) + { + var client = new HttpClient { BaseAddress = new Uri(baseUrl) }; + if (token != null) + { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token.Scheme, token.Value); + } + + return client; + } + + /// + /// Parses events from the based on the content type. + /// + /// The type of event to parse. + /// The containing the event data. + /// A list of parsed events. + private static List ParseEvents(HttpResponseMessage message) + where T : ITimeSeriesEvent + { + var contentType = message.Content.Headers.ContentType?.MediaType; + switch (contentType) + { + case "application/gzip": + { + using var stream = message.Content.ReadAsStream(); + using var gZipStream = new GZipStream(stream, CompressionMode.Decompress); + using var reader = new StreamReader(gZipStream); + return ParseEvents(reader); + } + case "application/zip": + { + var events = new List(); + using var stream = message.Content.ReadAsStream(); + using var archive = new ZipArchive(stream); + foreach (var entry in archive.Entries) + { + using var reader = new StreamReader(entry.Open()); + events.AddRange(ParseEvents(reader)); + } + + return events; + } + case "text/csv": + { + using var stream = message.Content.ReadAsStream(); + using var reader = new StreamReader(stream); + return ParseEvents(reader); + } + default: + throw new InvalidOperationException($"Unknown content type: {contentType}"); + } + } + + /// + /// Parses events from the provided . + /// + /// The type of event to parse. + /// The containing the event data. + /// A list of parsed events. + private static List ParseEvents(StreamReader reader) + where T : ITimeSeriesEvent + { + var config = new CsvConfiguration(InvariantCulture) + { + AllowComments = false, HasHeaderRecord = true, MissingFieldFound = null, DetectDelimiter = true, + }; + using var csv = new CsvReader(reader, config); + csv.Context.RegisterClassMap(); + csv.Context.RegisterClassMap(); + csv.Read(); + csv.ReadHeader(); + + // Check event type. + var expectedType = typeof(T).Name; + var actualType = csv.HeaderRecord?.First(); + if (string.IsNullOrEmpty(actualType) || !actualType.Contains(expectedType, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException( + $"Incorrect event type. Expected: {expectedType}, but found: {actualType ?? "null"}"); + } + + return csv.GetRecords().ToList(); + } +} diff --git a/samples/CandleDataResponseReader/TimeAndSaleMap.cs b/samples/CandleDataResponseReader/TimeAndSaleMap.cs new file mode 100644 index 00000000..5b8ab03b --- /dev/null +++ b/samples/CandleDataResponseReader/TimeAndSaleMap.cs @@ -0,0 +1,37 @@ +// +// 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/. +// + +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using CsvHelper.Configuration; +using DxFeed.Graal.Net.Events.Market; + +namespace DxFeed.Graal.Net.Samples; + +/// +/// A class map for the class, used to map CSV data to objects. +/// +[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Created by CsvHelper")] +internal sealed class TimeAndSaleMap : AbstractTimeSeriesMap +{ + /// + /// Initializes a new instance of the class. + /// This constructor configures the mapping rules for the class. + /// + public TimeAndSaleMap() + { + // Automatically map all fields using the given CSV configuration. + AutoMap(new CsvConfiguration(CultureInfo.InvariantCulture) { IgnoreReferences = true }); + Map(m => m.EventTime).TypeConverter(); + Map(m => m.Index).Name("Time").TypeConverter(); + Map(m => m.Time).Ignore(); // Time is processed in Index using TimeAndSequenceConverter. + Map(m => m.Sequence).Ignore(); // Sequence is processed in Index using TimeAndSequenceConverter. + Map(m => m.ExchangeSaleConditions).Name("SaleConditions"); + Map(m => m.Buyer).TypeConverter(); + Map(m => m.Seller).TypeConverter(); + Map(m => m.EventFlags).Optional().Convert(args => ParseEventFlags(args.Row)); + } +} diff --git a/src/DxFeed.Graal.Net/Events/Market/TimeAndSale.cs b/src/DxFeed.Graal.Net/Events/Market/TimeAndSale.cs index 31b7c7e5..2eb8f26b 100644 --- a/src/DxFeed.Graal.Net/Events/Market/TimeAndSale.cs +++ b/src/DxFeed.Graal.Net/Events/Market/TimeAndSale.cs @@ -271,7 +271,7 @@ public TimeAndSaleType Type /// Gets or sets implementation-specific flags. /// Do not use this method directly. /// - internal int Flags { get; set; } + public int Flags { get; set; } /// /// Returns string representation of this time and sale event.