Files
2026-07-13 13:02:50 +08:00

221 lines
8.4 KiB
C#

/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Newtonsoft.Json;
using NodaTime;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using static QuantConnect.StringExtensions;
namespace QuantConnect.Data.Custom.Tiingo
{
/// <summary>
/// Tiingo daily price data
/// https://api.tiingo.com/docs/tiingo/daily
/// </summary>
/// <remarks>Requires setting <see cref="Tiingo.AuthCode"/></remarks>
public class TiingoPrice : TradeBar
{
private readonly ConcurrentDictionary<string, DateTime> _startDates = new ConcurrentDictionary<string, DateTime>();
/// <summary>
/// The end time of this data. Some data covers spans (trade bars) and as such we want
/// to know the entire time span covered
/// </summary>
public override DateTime EndTime
{
get { return Time + Period; }
set { Time = value - Period; }
}
/// <summary>
/// The period of this trade bar, (second, minute, daily, ect...)
/// </summary>
public override TimeSpan Period => QuantConnect.Time.OneDay;
/// <summary>
/// The date this data pertains to
/// </summary>
[JsonProperty("date")]
public DateTime Date { get; set; }
/// <summary>
/// The actual (not adjusted) open price of the asset on the specific date
/// </summary>
[JsonProperty("open")]
public override decimal Open { get; set; }
/// <summary>
/// The actual (not adjusted) high price of the asset on the specific date
/// </summary>
[JsonProperty("high")]
public override decimal High { get; set; }
/// <summary>
/// The actual (not adjusted) low price of the asset on the specific date
/// </summary>
[JsonProperty("low")]
public override decimal Low { get; set; }
/// <summary>
/// The actual (not adjusted) closing price of the asset on the specific date
/// </summary>
[JsonProperty("close")]
public override decimal Close { get; set; }
/// <summary>
/// The actual (not adjusted) number of shares traded during the day
/// </summary>
[JsonProperty("volume")]
public override decimal Volume { get; set; }
/// <summary>
/// The adjusted opening price of the asset on the specific date. Returns null if not available.
/// </summary>
[JsonProperty("adjOpen")]
public decimal AdjustedOpen { get; set; }
/// <summary>
/// The adjusted high price of the asset on the specific date. Returns null if not available.
/// </summary>
[JsonProperty("adjHigh")]
public decimal AdjustedHigh { get; set; }
/// <summary>
/// The adjusted low price of the asset on the specific date. Returns null if not available.
/// </summary>
[JsonProperty("adjLow")]
public decimal AdjustedLow { get; set; }
/// <summary>
/// The adjusted close price of the asset on the specific date. Returns null if not available.
/// </summary>
[JsonProperty("adjClose")]
public decimal AdjustedClose { get; set; }
/// <summary>
/// The adjusted number of shares traded during the day - adjusted for splits. Returns null if not available
/// </summary>
[JsonProperty("adjVolume")]
public long AdjustedVolume { get; set; }
/// <summary>
/// The dividend paid out on "date" (note that "date" will be the "exDate" for the dividend)
/// </summary>
[JsonProperty("divCash")]
public decimal Dividend { get; set; }
/// <summary>
/// A factor used when a company splits or reverse splits. On days where there is ONLY a split (no dividend payment),
/// you can calculate the adjusted close as follows: adjClose = "Previous Close"/splitFactor
/// </summary>
[JsonProperty("splitFactor")]
public decimal SplitFactor { get; set; }
/// <summary>
/// Initializes an instance of the <see cref="TiingoPrice"/> class.
/// </summary>
public TiingoPrice()
{
Symbol = Symbol.Empty;
DataType = MarketDataType.Base;
}
/// <summary>
/// Return the URL string source of the file. This will be converted to a stream
/// </summary>
/// <param name="config">Configuration object</param>
/// <param name="date">Date of this source file</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>String URL of source file.</returns>
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
DateTime startDate;
if (!_startDates.TryGetValue(config.Symbol.Value, out startDate))
{
startDate = date;
_startDates.TryAdd(config.Symbol.Value, startDate);
}
var tiingoTicker = TiingoSymbolMapper.GetTiingoTicker(config.Symbol);
var source = Invariant($"https://api.tiingo.com/tiingo/daily/{tiingoTicker}/prices?startDate={startDate:yyyy-MM-dd}&token={Tiingo.AuthCode}");
return new SubscriptionDataSource(source, SubscriptionTransportMedium.RemoteFile, FileFormat.UnfoldingCollection);
}
/// <summary>
/// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method,
/// and returns a new instance of the object
/// each time it is called. The returned object is assumed to be time stamped in the config.ExchangeTimeZone.
/// </summary>
/// <param name="config">Subscription data config setup object</param>
/// <param name="line">Content of the source document</param>
/// <param name="date">Date of the requested data</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>
/// Instance of the T:BaseData object generated by this line of the CSV
/// </returns>
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
var list = JsonConvert.DeserializeObject<List<TiingoPrice>>(line);
foreach (var item in list)
{
item.Symbol = config.Symbol;
item.Time = item.Date;
item.Value = item.Close;
}
return new BaseDataCollection(date, config.Symbol, list);
}
/// <summary>
/// Indicates if there is support for mapping
/// </summary>
/// <returns>True indicates mapping should be used</returns>
public override bool RequiresMapping()
{
return true;
}
/// <summary>
/// Specifies the data time zone for this data type. This is useful for custom data types
/// </summary>
/// <returns>The <see cref="DateTimeZone"/> of this data type</returns>
public override DateTimeZone DataTimeZone()
{
return TimeZones.Utc;
}
/// <summary>
/// Gets the default resolution for this data and security type
/// </summary>
public override Resolution DefaultResolution()
{
return Resolution.Daily;
}
/// <summary>
/// Gets the supported resolution for this data and security type
/// </summary>
public override List<Resolution> SupportedResolutions()
{
return DailyResolution;
}
}
}