• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thorstenalpers / Finance.NET / 23104701747

15 Mar 2026 06:07AM UTC coverage: 93.634% (-0.8%) from 94.452%
23104701747

push

github

thorstenalpers
Update packages and exclude alpha vantage tests

501 of 615 branches covered (81.46%)

Branch coverage included in aggregate %.

1676 of 1710 relevant lines covered (98.01%)

7439.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.11
/src/Services/AlphaVantageService.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Net.Http;
5
using System.Threading;
6
using System.Threading.Tasks;
7
using Ardalis.GuardClauses;
8
using Finance.Net.Enums;
9
using Finance.Net.Exceptions;
10
using Finance.Net.Interfaces;
11
using Finance.Net.Models.AlphaVantage;
12
using Finance.Net.Utilities;
13
using Microsoft.Extensions.Logging;
14
using Microsoft.Extensions.Options;
15
using Newtonsoft.Json;
16
using Polly;
17
using Polly.Registry;
18

19
namespace Finance.Net.Services;
20
/// <inheritdoc />
21
public class AlphaVantageService : IAlphaVantageService
22
{
23
    private readonly ILogger<AlphaVantageService> _logger;
24
    private readonly IHttpClientFactory _httpClientFactory;
25
    private readonly FinanceNetConfiguration _options;
26
    private readonly IAsyncPolicy _retryPolicy;
27

28
    /// <inheritdoc />
29
    public AlphaVantageService(
26✔
30
        ILogger<AlphaVantageService> logger,
26✔
31
        IHttpClientFactory httpClientFactory,
26✔
32
        IOptions<FinanceNetConfiguration> options,
26✔
33
        IReadOnlyPolicyRegistry<string>? policyRegistry = null)
26✔
34
    {
35
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
26✔
36
        _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
25✔
37
        _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
24✔
38

39
        if (policyRegistry != null &&
23!
40
            policyRegistry.TryGet<IAsyncPolicy>(Constants.DefaultHttpRetryPolicy, out var found))
23✔
41
        {
42
            _retryPolicy = found;
×
43
        }
44
        else
45
        {
46
            _retryPolicy = Policy
23✔
47
                .Handle<HttpRequestException>()
23✔
48
                .Or<TaskCanceledException>()
23✔
49
                .WaitAndRetryAsync(
23✔
50
                    3,
23✔
51
                    attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt - 1)),
×
52
                    (ex, ts, retryCount, ctx) =>
23✔
53
                    {
23✔
54
                        _logger.LogWarning(
×
55
                            "Retry {RetryCount} wegen {Reason}, warte {Delay}s",
×
56
                            retryCount,
×
57
                            ex?.Message ?? "Unbekannt",
×
58
                            ts.TotalSeconds);
×
59
                    });
23✔
60

61
            _logger.LogWarning(
23✔
62
                "Retry-Policy '{PolicyKey}' nicht gefunden – verwende Default (3x Retry, Backoff).",
23✔
63
                Constants.DefaultHttpRetryPolicy);
23✔
64
        }
65
    }
23✔
66

67
    /// <inheritdoc />
68
    public async Task<InstrumentOverview?> GetOverviewAsync(string symbol, CancellationToken token = default)
69
    {
70
        var httpClient = _httpClientFactory.CreateClient(Constants.AlphaVantageHttpClientName);
4✔
71
        var url = Constants.AlphaVantageApiBaseUrl + "/query?function=OVERVIEW" +
4✔
72
            $"&symbol={symbol}" +
4✔
73
            $"&apikey={_options.AlphaVantageApiKey}";
4✔
74

75
        try
76
        {
77
            var instrumentOverview = await _retryPolicy.ExecuteAsync(async () =>
4✔
78
                {
4✔
79
                    var httpResponse = await httpClient.GetAsync(url, token).ConfigureAwait(false);
4✔
80
                    httpResponse.EnsureSuccessStatusCode();
4✔
81

4✔
82
                    var jsonResponse = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
4✔
83
                    if (jsonResponse.Contains(Constants.ApiResponseLimitExceeded))
4✔
84
                    {
4✔
85
                        throw new FinanceNetException($"{Constants.ApiResponseLimitExceeded} for {symbol}");
1✔
86
                    }
4✔
87
                    else
4✔
88
                    {
4✔
89
                        var overview = JsonConvert.DeserializeObject<InstrumentOverview>(jsonResponse);
3✔
90
                        var isNullObj = Helper.AreAllPropertiesNull(overview);
3✔
91
                        return isNullObj ? throw new FinanceNetException(Constants.ValidationMessageAllFieldsEmpty) : overview;
3✔
92
                    }
4✔
93
                }).ConfigureAwait(false);
5✔
94
            return instrumentOverview;
1✔
95
        }
96
        catch (Exception ex)
3✔
97
        {
98
            throw new FinanceNetException($"No overview found for {symbol}", ex);
3✔
99
        }
100
    }
1✔
101

102
    /// <inheritdoc />
103
    public async Task<IEnumerable<Record>> GetRecordsAsync(string symbol, DateTime? startDate = null, DateTime? endDate = null, CancellationToken token = default)
104
    {
105
        var httpClient = _httpClientFactory.CreateClient(Constants.AlphaVantageHttpClientName);
6✔
106
        var url = Constants.AlphaVantageApiBaseUrl + "/query?function=TIME_SERIES_DAILY_ADJUSTED" +
6✔
107
            $"&symbol={symbol}&outputsize=full&apikey={_options.AlphaVantageApiKey}";
6✔
108
        Guard.Against.NullOrEmpty(symbol);
6✔
109

110
        startDate ??= DateTime.UtcNow.AddDays(-7).Date;
6!
111
        endDate ??= DateTime.UtcNow;
6✔
112
        if (startDate > endDate)
6!
113
        {
114
            throw new FinanceNetException("startDate earlier than endDate");
1✔
115
        }
116
        if (endDate.Value.Date >= DateTime.UtcNow.Date)
5✔
117
        {
118
            endDate = DateTime.UtcNow.Date;
5✔
119
        }
120
        try
121
        {
122
            return await _retryPolicy.ExecuteAsync(async () =>
5✔
123
            {
5✔
124
                var jsonResponse = await Helper.FetchJsonDocumentAsync(httpClient, _logger, url, token).ConfigureAwait(false);
5✔
125
                if (jsonResponse.Contains(Constants.ApiResponseLimitExceeded))
5✔
126
                {
5✔
127
                    throw new FinanceNetException($"{Constants.ApiResponseLimitExceeded} for {symbol}");
1✔
128
                }
5✔
129
                var result = AlphaVantageParser.ParseRecords(symbol, startDate, endDate, jsonResponse, _logger);
4✔
130
                return result.IsNullOrEmpty() ? throw new FinanceNetException(Constants.ValidationMessageAllFieldsEmpty) : result;
3✔
131
            }).ConfigureAwait(false);
7✔
132
        }
133
        catch (Exception ex)
3✔
134
        {
135
            throw new FinanceNetException($"No Record found for {symbol}", ex);
3✔
136
        }
137
    }
2✔
138

139

140

141
    /// <inheritdoc />
142
    public async Task<IEnumerable<IntradayRecord>> GetIntradayRecordsAsync(string symbol, DateTime startDate, DateTime? endDate = null, EInterval interval = EInterval.Interval_15Min, CancellationToken token = default)
143
    {
144
        Guard.Against.NullOrEmpty(symbol);
6✔
145
        var result = new List<IntradayRecord>();
6✔
146
        if (startDate > endDate)
6✔
147
        {
148
            throw new FinanceNetException("startDate earlier than endDate");
1✔
149
        }
150
        endDate ??= DateTime.UtcNow.Date;
5✔
151
        if (endDate.Value.Date >= DateTime.UtcNow.Date)
5✔
152
        {
153
            endDate = DateTime.UtcNow.Date;
4✔
154
        }
155

156
        for (var currentMonth = new DateTime(startDate.Year, startDate.Month, 1, 0, 0, 0, DateTimeKind.Utc); currentMonth <= endDate; currentMonth = currentMonth.AddMonths(1))
64!
157
        {
158
            if (currentMonth == endDate &&
31!
159
                ((endDate.Value.Day == 1 && endDate.Value.DayOfWeek == DayOfWeek.Saturday) ||
31✔
160
                    (endDate.Value.Day == 1 && endDate.Value.DayOfWeek == DayOfWeek.Sunday) ||
31✔
161
                    (endDate.Value.Day == 2 && endDate.Value.DayOfWeek == DayOfWeek.Sunday)))
31✔
162
            {
163
                // dont query for data which not exists (api exception)
164
                break;
165
            }
166
            var currentCourses = await GetIntradayRecordsByMonthAsync(symbol, currentMonth, interval, token).ConfigureAwait(false);
31✔
167
            result.AddRange(currentCourses);
27✔
168
        }
169
        result = result.Where(e => e.DateTime >= startDate).ToList();
2,701✔
170
        return result.IsNullOrEmpty() ? throw new FinanceNetException(Constants.ValidationMessageAllFieldsEmpty) : result;
1!
171
    }
1✔
172

173
    private async Task<List<IntradayRecord>> GetIntradayRecordsByMonthAsync(string symbol, DateTime month, EInterval interval, CancellationToken token = default)
174
    {
175
        var httpClient = _httpClientFactory.CreateClient(Constants.AlphaVantageHttpClientName);
31✔
176
        var url = Constants.AlphaVantageApiBaseUrl + "/query?function=TIME_SERIES_INTRADAY" +
31✔
177
            $"&symbol={symbol}" +
31✔
178
            $"&interval={interval.GetDescription()}" +
31✔
179
            $"&month={month:yyyy-MM}" +
31✔
180
            "&extended_hours=false" +      // no pre and post market trading
31✔
181
            "&outputsize=full" +
31✔
182
            $"&apikey={_options.AlphaVantageApiKey}";
31✔
183

184
        try
185
        {
186
            return await _retryPolicy.ExecuteAsync(async () =>
31✔
187
            {
31✔
188
                var response = await httpClient.GetAsync(url, token).ConfigureAwait(false);
31✔
189
                response.EnsureSuccessStatusCode();
31✔
190

31✔
191
                var jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
31✔
192
                if (jsonResponse.Contains(Constants.ApiResponseLimitExceeded))
31✔
193
                {
31✔
194
                    throw new FinanceNetException($"{Constants.ApiResponseLimitExceeded} for {symbol}");
1✔
195
                }
31✔
196
                var result = AlphaVantageParser.ParseIntradayRecords(symbol, interval, jsonResponse);
30✔
197
                return result.IsNullOrEmpty() ? throw new FinanceNetException(Constants.ValidationMessageAllFieldsEmpty) : result;
28✔
198
            }).ConfigureAwait(false);
58✔
199
        }
200
        catch (Exception ex)
4✔
201
        {
202
            throw new FinanceNetException($"No intraday record found for {symbol}", ex);
4✔
203
        }
204
    }
27✔
205

206

207
    /// <inheritdoc />
208
    public async Task<IEnumerable<ForexRecord>> GetForexRecordsAsync(string currency1, string currency2, DateTime startDate, DateTime? endDate = null, CancellationToken token = default)
209
    {
210
        var httpClient = _httpClientFactory.CreateClient(Constants.AlphaVantageHttpClientName);
6✔
211
        Guard.Against.NullOrEmpty(currency1);
6✔
212
        Guard.Against.NullOrEmpty(currency2);
6✔
213
        if (startDate > endDate)
6✔
214
        {
215
            throw new FinanceNetException("startDate earlier than endDate");
1✔
216
        }
217

218
        endDate ??= DateTime.UtcNow.Date;
5✔
219
        if (endDate.Value.Date >= DateTime.UtcNow.Date)
5✔
220
        {
221
            endDate = DateTime.UtcNow.Date;
5✔
222
        }
223
        var url = Constants.AlphaVantageApiBaseUrl + "/query?function=FX_DAILY" +
5✔
224
            $"&from_symbol={currency1}&to_symbol={currency2}&outputsize=full&apikey={_options.AlphaVantageApiKey}";
5✔
225

226
        try
227
        {
228
            return await _retryPolicy.ExecuteAsync(async () =>
5✔
229
            {
5✔
230
                var jsonResponse = await Helper.FetchJsonDocumentAsync(httpClient, _logger, url, token).ConfigureAwait(false);
5✔
231
                if (jsonResponse.Contains(Constants.ApiResponseLimitExceeded))
5✔
232
                {
5✔
233
                    throw new FinanceNetException($"{Constants.ApiResponseLimitExceeded} for {currency1} /{currency2}");
1✔
234
                }
5✔
235
                if (jsonResponse.Contains(Constants.ApiResponseApiKeyInvalid))
4!
236
                {
5✔
237
                    throw new FinanceNetException($"{Constants.ApiResponseApiKeyInvalid}");
×
238
                }
5✔
239
                var result = AlphaVantageParser.ParseForexRecords(currency1, currency2, startDate, endDate, jsonResponse, _logger);
4✔
240
                return result.IsNullOrEmpty() ? throw new FinanceNetException(Constants.ValidationMessageAllFieldsEmpty) : result;
3✔
241
            }).ConfigureAwait(false);
7✔
242
        }
243
        catch (Exception ex)
3✔
244
        {
245
            throw new FinanceNetException($"No forex record found for {currency1}, {currency2}", ex);
3✔
246
        }
247
    }
2✔
248
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc