Skip to content

Commit

Permalink
Merge pull request #23 from jhonabreul/feature-null-history-for-unsup…
Browse files Browse the repository at this point in the history
…ported-securities

Return null on unsupported history and data download requests
  • Loading branch information
jhonabreul authored Feb 27, 2024
2 parents 003a55a + 5d16d8e commit 6c93dcc
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using QuantConnect.Data.Market;
using QuantConnect.Logging;
using QuantConnect.Securities;
using QuantConnect.Tests.Engine.DataFeeds;
using System;

namespace QuantConnect.Tests.Brokerages.Samco
Expand All @@ -34,61 +35,77 @@ private static TestCaseData[] TestParameters
return new[]
{
// valid parameters
new TestCaseData(Symbols.SBIN, Resolution.Tick, Time.OneMinute, false),
new TestCaseData(Symbols.SBIN, Resolution.Second, Time.OneMinute, false),
new TestCaseData(Symbols.SBIN, Resolution.Minute, Time.OneHour, false),
new TestCaseData(Symbols.SBIN, Resolution.Hour, Time.OneDay, false),
new TestCaseData(Symbols.SBIN, Resolution.Daily, TimeSpan.FromDays(15), false),
new TestCaseData(Symbols.SBIN, Resolution.Daily, TimeSpan.FromDays(-15), false)
new TestCaseData(Symbols.SBIN, Resolution.Minute, TickType.Trade, Time.OneHour, false),

// invalid resolution
new TestCaseData(Symbols.SBIN, Resolution.Tick, TickType.Trade, Time.OneMinute, true),
new TestCaseData(Symbols.SBIN, Resolution.Second, TickType.Trade, Time.OneMinute, true),
new TestCaseData(Symbols.SBIN, Resolution.Hour, TickType.Trade, Time.OneDay, true),
new TestCaseData(Symbols.SBIN, Resolution.Daily, TickType.Trade, TimeSpan.FromDays(15), true),

// invalid tick type
new TestCaseData(Symbols.SBIN, Resolution.Minute, TickType.Quote, Time.OneHour, true),
new TestCaseData(Symbols.SBIN, Resolution.Minute, TickType.OpenInterest, Time.OneHour, true),

// invalid security type
new TestCaseData(Symbols.USDJPY, Resolution.Minute, TickType.Trade, Time.OneHour, true),

// invalid market
new TestCaseData(Symbol.Create("SBIN", SecurityType.Equity, Market.USA), Resolution.Minute, TickType.Trade, Time.OneHour, true),

// invalid time range
new TestCaseData(Symbols.SBIN, Resolution.Minute, TickType.Trade, TimeSpan.FromDays(-15), true),
};
}
}

[Test, TestCaseSource(nameof(TestParameters))]
public void GetsHistory(Symbol symbol, Resolution resolution, TimeSpan period, bool throwsException)
public void GetsHistory(Symbol symbol, Resolution resolution, TickType tickType, TimeSpan period, bool unsupported)
{
TestDelegate test = () =>
{
var apiSecret = Config.Get("samco-client-password");
var apiKey = Config.Get("samco-client-id");
var yob = Config.Get("samco-year-of-birth");
var tradingSegment = Config.Get("samco-trading-segment");
var productType = Config.Get("samco-product-type");
var brokerage = new SamcoBrokerage(tradingSegment, productType, apiKey, apiSecret, yob, null, null);

var now = DateTime.UtcNow;

var request = new HistoryRequest(now.Add(-period),
now,
typeof(TradeBar),
symbol,
resolution,
SecurityExchangeHours.AlwaysOpen(TimeZones.Kolkata),
TimeZones.Kolkata,
Resolution.Minute,
false,
false,
DataNormalizationMode.Adjusted,
TickType.Trade);

var history = brokerage.GetHistory(request);

foreach (var slice in history)
{
Log.Trace("{0}: {1} - {2} / {3}", slice.Time, slice.Symbol, slice.Price, slice.IsFillForward);
}
var apiSecret = Config.Get("samco-client-password");
var apiKey = Config.Get("samco-client-id");
var yob = Config.Get("samco-year-of-birth");
var tradingSegment = Config.Get("samco-trading-segment");
var productType = Config.Get("samco-product-type");

Log.Trace("Base currency: " + brokerage.AccountBaseCurrency);
};
var now = DateTime.UtcNow;

if (throwsException)
var algorithm = new AlgorithmStub();
algorithm.Portfolio = new SecurityPortfolioManager(new SecurityManager(new TimeKeeper(now)), algorithm.Transactions, algorithm.Settings);
var security = algorithm.AddSecurity(symbol);
algorithm.Portfolio.Securities.Add(security);

var brokerage = new SamcoBrokerage(tradingSegment, productType, apiKey, apiSecret, yob, algorithm, null);

var request = new HistoryRequest(now.Add(-period),
now,
typeof(TradeBar),
symbol,
resolution,
SecurityExchangeHours.AlwaysOpen(TimeZones.Kolkata),
TimeZones.Kolkata,
Resolution.Minute,
false,
false,
DataNormalizationMode.Adjusted,
tickType);

var history = brokerage.GetHistory(request);

if (unsupported)
{
Assert.Throws<ArgumentException>(test);
Assert.IsNull(history);
return;
}
else

Assert.IsNotNull(history);

foreach (var slice in history)
{
Assert.DoesNotThrow(test);
Log.Trace("{0}: {1} - {2} / {3}", slice.Time, slice.Symbol, slice.Price, slice.IsFillForward);
}

Log.Trace("Base currency: " + brokerage.AccountBaseCurrency);
}
}
}
77 changes: 50 additions & 27 deletions QuantConnect.SamcoBrokerage/SamcoBrokerage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ public partial class SamcoBrokerage : Brokerage, IDataQueueHandler
private Task _checkConnectionTask;
private bool _isInitialized;

private bool _loggedInvalidTickTypeForHistory;
private bool _loggedInvalidSecurityTypeForHistory;
private bool _loggedInvalidResolutionForHistory;
private bool _loggedInvalidTimeRangeForHistory;
private bool _loggedInvalidMarketForHistory;

/// <summary>
/// The websockets client instance
/// </summary>
Expand Down Expand Up @@ -346,52 +352,69 @@ public override IEnumerable<BaseData> GetHistory(HistoryRequest request)
// Samco API only allows us to support history requests for TickType.Trade
if (request.TickType != TickType.Trade)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidTickType",
$"{request.TickType} TickType not supported, no history returned"));
yield break;
if (!_loggedInvalidTickTypeForHistory)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidTickType",
$"{request.TickType} TickType not supported, no history returned"));
_loggedInvalidTickTypeForHistory = true;
}
return null;
}

if (request.Symbol.SecurityType != SecurityType.Equity && request.Symbol.SecurityType != SecurityType.Future && request.Symbol.SecurityType != SecurityType.Option)
if (request.Symbol.SecurityType != SecurityType.Equity &&
request.Symbol.SecurityType != SecurityType.Future &&
request.Symbol.SecurityType != SecurityType.Option)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidSecurityType",
$"{request.Symbol.SecurityType} security type not supported, no history returned"));
yield break;
if (!_loggedInvalidSecurityTypeForHistory)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidSecurityType",
$"{request.Symbol.SecurityType} security type not supported, no history returned"));
_loggedInvalidSecurityTypeForHistory = true;
}
return null;
}

if (request.Resolution == Resolution.Tick || request.Resolution == Resolution.Second)
if (request.Symbol.ID.Market != Market.India)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidResolution",
$"{request.Resolution} resolution not supported, no history returned"));
yield break;
if (!_loggedInvalidMarketForHistory)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidMarket",
$"{request.Symbol.ID.Market} market not supported, no history returned"));
_loggedInvalidMarketForHistory = true;
}
return null;
}

if (request.StartTimeUtc >= request.EndTimeUtc)
if (request.Resolution != Resolution.Minute)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidDateRange",
"The history request start date must precede the end date, no history returned"));
yield break;
if (!_loggedInvalidResolutionForHistory)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidResolution",
$"{request.Resolution} resolution not supported, no history returned"));
_loggedInvalidResolutionForHistory = true;
}
return null;
}

if (request.Resolution != Resolution.Minute)
if (request.StartTimeUtc >= request.EndTimeUtc)
{
throw new ArgumentException($"SamcoBrokerage.ConvertResolution: Unsupported resolution type: {request.Resolution}");
if (!_loggedInvalidTimeRangeForHistory)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidDateRange",
"The history request start date must precede the end date, no history returned"));
_loggedInvalidTimeRangeForHistory = true;
}
return null;
}

var leanSymbol = request.Symbol;
var securityExchange = _securityProvider.GetSecurity(leanSymbol).Exchange;
var exchange = _symbolMapper.GetExchange(leanSymbol);
var isIndex = leanSymbol.SecurityType == SecurityType.Index;

var history = _samcoAPI.GetIntradayCandles(request.Symbol, exchange, request.StartTimeLocal, request.EndTimeLocal, request.Resolution, isIndex);

foreach (var baseData in history)
{
if (!securityExchange.DateTimeIsOpen(baseData.Time) && !request.IncludeExtendedMarketHours)
{
continue;
}
yield return baseData;
}
return _samcoAPI
.GetIntradayCandles(request.Symbol, exchange, request.StartTimeLocal, request.EndTimeLocal, request.Resolution, isIndex)
.Where(baseData => securityExchange.DateTimeIsOpen(baseData.Time) || request.IncludeExtendedMarketHours);
}

/// <summary>
Expand Down

0 comments on commit 6c93dcc

Please sign in to comment.