Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d10ec4186 |
@@ -75,13 +75,13 @@ namespace QuantConnect
|
||||
/// <summary>
|
||||
/// The backtest start date
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.UI)]
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The backtest end date
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.UI)]
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace QuantConnect.Api
|
||||
/// <summary>
|
||||
/// Backtest creation date and time
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.UI)]
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace QuantConnect.Api
|
||||
/// <summary>
|
||||
/// End date of out of sample data
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.UI)]
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
|
||||
public DateTime? OutOfSampleMaxEndDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -87,7 +87,7 @@ namespace QuantConnect.Api
|
||||
/// <summary>
|
||||
/// Date when this optimization was created
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.UI)]
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace QuantConnect.Api
|
||||
/// <summary>
|
||||
/// Optimization requested date and time
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.UI)]
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
|
||||
public DateTime Requested { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -76,19 +76,19 @@ namespace QuantConnect.Api
|
||||
if (optimizationBacktest.StartDate != default)
|
||||
{
|
||||
writer.WritePropertyName("startDate");
|
||||
writer.WriteValue(optimizationBacktest.StartDate.ToStringInvariant(DateFormat.UI));
|
||||
writer.WriteValue(optimizationBacktest.StartDate.ToStringInvariant(DateFormat.ISOShort));
|
||||
}
|
||||
|
||||
if (optimizationBacktest.EndDate != default)
|
||||
{
|
||||
writer.WritePropertyName("endDate");
|
||||
writer.WriteValue(optimizationBacktest.EndDate.ToStringInvariant(DateFormat.UI));
|
||||
writer.WriteValue(optimizationBacktest.EndDate.ToStringInvariant(DateFormat.ISOShort));
|
||||
}
|
||||
|
||||
if (optimizationBacktest.OutOfSampleMaxEndDate != null)
|
||||
{
|
||||
writer.WritePropertyName("outOfSampleMaxEndDate");
|
||||
writer.WriteValue(optimizationBacktest.OutOfSampleMaxEndDate.ToStringInvariant(DateFormat.UI));
|
||||
writer.WriteValue(optimizationBacktest.OutOfSampleMaxEndDate.ToStringInvariant(DateFormat.ISOShort));
|
||||
|
||||
writer.WritePropertyName("outOfSampleDays");
|
||||
writer.WriteValue(optimizationBacktest.OutOfSampleDays);
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace QuantConnect
|
||||
public const string DB = "yyyy-MM-dd HH:mm:ss";
|
||||
/// QuantConnect UX Date Representation
|
||||
public const string UI = "yyyy-MM-dd HH:mm:ss";
|
||||
/// ISO Date Representation
|
||||
public const string ISOShort = "yyyy-MM-ddTHH:mm:ssZ";
|
||||
/// en-US Short Date and Time Pattern
|
||||
public const string USShort = "M/d/yy h:mm tt";
|
||||
/// en-US Short Date Pattern
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
@@ -14,22 +14,93 @@
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a json converter that allows defining the date time format used
|
||||
/// </summary>
|
||||
public class DateTimeJsonConverter : IsoDateTimeConverter
|
||||
public class DateTimeJsonConverter : JsonConverter
|
||||
{
|
||||
private readonly List<IsoDateTimeConverter> _converters;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeJsonConverter"/> class
|
||||
/// True, can read a json into a date time
|
||||
/// </summary>
|
||||
/// <param name="format">The date time format</param>
|
||||
public override bool CanRead => true;
|
||||
|
||||
/// <summary>
|
||||
/// True, can write a datetime to json
|
||||
/// </summary>
|
||||
public override bool CanWrite => true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeJsonConverter"/> class
|
||||
/// </summary>
|
||||
/// <param name="format">>The date time format</param>
|
||||
public DateTimeJsonConverter(string format)
|
||||
{
|
||||
DateTimeFormat = format;
|
||||
_converters = [new IsoDateTimeConverter() { DateTimeFormat = format }];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeJsonConverter"/> class
|
||||
/// </summary>
|
||||
/// <param name="format">>The date time format</param>
|
||||
/// <param name="format2">Other format for backwards compatibility</param>
|
||||
public DateTimeJsonConverter(string format, string format2) : this(format)
|
||||
{
|
||||
_converters.Add(new IsoDateTimeConverter() { DateTimeFormat = format2 });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeJsonConverter"/> class
|
||||
/// </summary>
|
||||
/// <param name="format">>The date time format</param>
|
||||
/// <param name="format2">Other format for backwards compatibility</param>
|
||||
/// <param name="format3">Other format for backwards compatibility</param>
|
||||
public DateTimeJsonConverter(string format, string format2, string format3) : this(format, format2)
|
||||
{
|
||||
_converters.Add(new IsoDateTimeConverter() { DateTimeFormat = format3 });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if can convert the given object type
|
||||
/// </summary>
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(DateTime) || objectType == typeof(string) || objectType == typeof(DateTime?);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given value
|
||||
/// </summary>
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
foreach (var converter in _converters)
|
||||
{
|
||||
try
|
||||
{
|
||||
return converter.ReadJson(reader, objectType, existingValue, serializer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
throw new JsonSerializationException($"Unexpected value when converting date. Expected formats: {string.Join(",", _converters.Select(x => x.DateTimeFormat))}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given value to json
|
||||
/// </summary>
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
_converters[0].WriteJson(writer, value, serializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ namespace QuantConnect.Lean.Engine.Results
|
||||
ResultsDestinationFolder = Globals.ResultsDestinationFolder;
|
||||
State = new Dictionary<string, string>
|
||||
{
|
||||
["StartTime"] = StartTime.ToStringInvariant(DateFormat.UI),
|
||||
["StartTime"] = StartTime.ToStringInvariant(DateFormat.ISOShort),
|
||||
["EndTime"] = string.Empty,
|
||||
["RuntimeError"] = string.Empty,
|
||||
["StackTrace"] = string.Empty,
|
||||
@@ -879,7 +879,7 @@ namespace QuantConnect.Lean.Engine.Results
|
||||
{
|
||||
State["Status"] = Algorithm.Status.ToStringInvariant();
|
||||
}
|
||||
State["EndTime"] = endTime != null ? endTime.ToStringInvariant(DateFormat.UI) : string.Empty;
|
||||
State["EndTime"] = endTime != null ? endTime.ToStringInvariant(DateFormat.ISOShort) : string.Empty;
|
||||
|
||||
lock (LogStore)
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace QuantConnect.Optimizer
|
||||
/// <summary>
|
||||
/// The creation time
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.UI)]
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI)]
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace QuantConnect.Tests.API
|
||||
public class OptimizationBacktestJsonConverterTests
|
||||
{
|
||||
private const string _validSerialization = "{\"name\":\"ImABacktestName\",\"id\":\"backtestId\",\"progress\":0.0,\"exitCode\":0," +
|
||||
"\"startDate\":\"2023-01-01 00:00:00\",\"endDate\":\"2024-01-01 00:00:00\",\"outOfSampleMaxEndDate\":\"2024-01-01 00:00:00\",\"outOfSampleDays\":10,\"statistics\":[0.374,0.217,0.047,-4.51,2.86,-0.664,52.602,17.800,6300000.00,0.196,1.571,27.0,123.888,77.188,0.63,1.707,1390.49,180.0,0.233,-0.558,73.0]," +
|
||||
"\"startDate\":\"2023-01-01T00:00:00Z\",\"endDate\":\"2024-01-01T00:00:00Z\",\"outOfSampleMaxEndDate\":\"2024-01-01T00:00:00Z\",\"outOfSampleDays\":10,\"statistics\":[0.374,0.217,0.047,-4.51,2.86,-0.664,52.602,17.800,6300000.00,0.196,1.571,27.0,123.888,77.188,0.63,1.707,1390.49,180.0,0.233,-0.558,73.0]," +
|
||||
"\"parameterSet\":{\"pinocho\":\"19\",\"pepe\":\"-1\"},\"equity\":[[1,1.0,1.0,1.0,1.0],[2,2.0,2.0,2.0,2.0],[3,3.0,3.0,3.0,3.0]]}";
|
||||
private const string _oldValidSerialization = "{\"name\":\"ImABacktestName\",\"id\":\"backtestId\",\"progress\":0.0,\"exitCode\":0," +
|
||||
"\"statistics\":[0.374,0.217,0.047,-4.51,2.86,-0.664,52.602,17.800,6300000.00,0.196,1.571,27.0,123.888,77.188,0.63,1.707,1390.49,180.0,0.233,-0.558,73.0]," +
|
||||
|
||||
@@ -82,13 +82,13 @@ namespace QuantConnect.Tests.Common
|
||||
{
|
||||
serialized = $"{{\"Name\":\"Backtest name\",\"Tags\":[\"tag1\",\"tag2\"],\"AccountCurrency\":\"GBP\",\"Brokerage\":32," +
|
||||
$"\"AccountType\":1,\"Parameters\":{{\"a\":\"A\",\"b\":\"B\"}},\"OutOfSampleMaxEndDate\":\"2023-01-01T00:00:00\"," +
|
||||
$"\"OutOfSampleDays\":30,\"StartDate\":\"1998-01-01 00:00:00\",\"EndDate\":\"{algorithm.EndDate.ToString(DateFormat.UI)}\",\"TradingDaysPerYear\":252}}";
|
||||
$"\"OutOfSampleDays\":30,\"StartDate\":\"1998-01-01T00:00:00Z\",\"EndDate\":\"{algorithm.EndDate.ToString(DateFormat.ISOShort)}\",\"TradingDaysPerYear\":252}}";
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual($"{{\"name\":\"Backtest name\",\"tags\":[\"tag1\",\"tag2\"],\"accountCurrency\":\"GBP\",\"brokerage\":32," +
|
||||
$"\"accountType\":1,\"parameters\":{{\"a\":\"A\",\"b\":\"B\"}},\"outOfSampleMaxEndDate\":\"2023-01-01T00:00:00\"," +
|
||||
$"\"outOfSampleDays\":30,\"startDate\":\"1998-01-01 00:00:00\",\"endDate\":\"{algorithm.EndDate.ToString(DateFormat.UI)}\",\"tradingDaysPerYear\":252}}", serialized);
|
||||
$"\"outOfSampleDays\":30,\"startDate\":\"1998-01-01T00:00:00Z\",\"endDate\":\"{algorithm.EndDate.ToString(DateFormat.ISOShort)}\",\"tradingDaysPerYear\":252}}", serialized);
|
||||
}
|
||||
|
||||
var deserialize = JsonConvert.DeserializeObject<AlgorithmConfiguration>(serialized);
|
||||
|
||||
127
Tests/Common/Util/DateTimeJsonConverterTests.cs
Normal file
127
Tests/Common/Util/DateTimeJsonConverterTests.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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 Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Tests.Common.Util
|
||||
{
|
||||
[TestFixture]
|
||||
public class DateTimeJsonConverterTests
|
||||
{
|
||||
[Test]
|
||||
public void Write()
|
||||
{
|
||||
var instance = new MultiDateTimeFormatClassTest { Value = new DateTime(2025, 08, 08, 10, 30, 0) };
|
||||
var result = JsonConvert.SerializeObject(instance);
|
||||
Assert.AreEqual("{\"Value\":\"2025-08-08T10:30:00Z\"}", result);
|
||||
}
|
||||
|
||||
[TestCase("{ \"value\": \"2025-08-08 10:30:00\"}", false)]
|
||||
[TestCase("{ \"value\": \"2025-08-08T10:30:00Z\"}", false)]
|
||||
[TestCase("{ \"value\": \"2025-08-08T10:30:00.000Z\"}", false)]
|
||||
[TestCase("{ \"value\": \"20250808-10:30:00\"}", false)]
|
||||
|
||||
[TestCase("{ \"value\": \"2025/08/08 10:30:00.000\"}", true)]
|
||||
[TestCase("{ \"value\": \"2025-08-08 10:30:00.000\"}", true)]
|
||||
public void MultipleFormats(string strObject, bool throws)
|
||||
{
|
||||
if (throws)
|
||||
{
|
||||
Assert.Throws<JsonSerializationException>(() => JsonConvert.DeserializeObject<SingleDateTimeFormatClassTest>(strObject));
|
||||
return;
|
||||
}
|
||||
var result = JsonConvert.DeserializeObject<MultiDateTimeFormatClassTest>(strObject);
|
||||
|
||||
Assert.AreEqual(new DateTime(2025, 08, 08, 10, 30, 0), result.Value);
|
||||
}
|
||||
|
||||
[TestCase("{ \"value\": \"2025-08-08 10:30:00\"}", false)]
|
||||
[TestCase("{ \"value\": \"2025-08-08T10:30:00\"}", false)]
|
||||
[TestCase("{ \"value\": \"2025-08-08T10:30:00Z\"}", false)]
|
||||
|
||||
[TestCase("{ \"value\": \"2025/08/08 10:30:00\"}", true)]
|
||||
public void SingleFormat(string strObject, bool throws)
|
||||
{
|
||||
if (throws)
|
||||
{
|
||||
Assert.Throws<JsonSerializationException>(() => JsonConvert.DeserializeObject<SingleDateTimeFormatClassTest>(strObject));
|
||||
return;
|
||||
}
|
||||
var result = JsonConvert.DeserializeObject<SingleDateTimeFormatClassTest>(strObject);
|
||||
|
||||
Assert.AreEqual(new DateTime(2025, 08, 08, 10, 30, 0), result.Value);
|
||||
}
|
||||
|
||||
[TestCase("{ \"value\": \"2025-08-08T10:30:00\"}", false, false)]
|
||||
[TestCase("{ \"value\": \"2025-08-08T10:30:00Z\"}", false, false)]
|
||||
[TestCase("{ \"value\": null}", false, true)]
|
||||
[TestCase("{ }", false, true)]
|
||||
|
||||
[TestCase("{ \"value\": \"2025/08/08 10:30:00\"}", true, false)]
|
||||
[TestCase("{ \"value\": \"2025-08-08 10:30:00\"}", true, false)]
|
||||
public void NullFormat(string strObject, bool throws, bool expectNull)
|
||||
{
|
||||
if (throws)
|
||||
{
|
||||
Assert.Throws<JsonSerializationException>(() => JsonConvert.DeserializeObject<NullableDateTimeFormatClassTest>(strObject));
|
||||
return;
|
||||
}
|
||||
var result = JsonConvert.DeserializeObject<NullableDateTimeFormatClassTest>(strObject);
|
||||
if (expectNull)
|
||||
{
|
||||
Assert.AreEqual(null, result.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.AreEqual(new DateTime(2025, 08, 08, 10, 30, 0), result.Value);
|
||||
}
|
||||
|
||||
[TestCase("\"2025-08-08T10:30:00Z\"", false)]
|
||||
[TestCase("\"2025-08-08 10:30:00\"", false)]
|
||||
[TestCase("", true)]
|
||||
[TestCase("\"\"", true)]
|
||||
public void ManualConversion(string strObject, bool expectNull)
|
||||
{
|
||||
var converter = new DateTimeJsonConverter(DateFormat.ISOShort, DateFormat.UI);
|
||||
var result = JsonConvert.DeserializeObject<DateTime?>(strObject, converter);
|
||||
if (expectNull)
|
||||
{
|
||||
Assert.AreEqual(null, result);
|
||||
return;
|
||||
}
|
||||
Assert.AreEqual(new DateTime(2025, 08, 08, 10, 30, 0), result.Value);
|
||||
}
|
||||
|
||||
internal class MultiDateTimeFormatClassTest
|
||||
{
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort, DateFormat.UI, DateFormat.FIX)]
|
||||
public DateTime Value { get; set; }
|
||||
}
|
||||
internal class SingleDateTimeFormatClassTest
|
||||
{
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.UI)]
|
||||
public DateTime Value { get; set; }
|
||||
}
|
||||
internal class NullableDateTimeFormatClassTest
|
||||
{
|
||||
[JsonConverter(typeof(DateTimeJsonConverter), DateFormat.ISOShort)]
|
||||
public DateTime? Value { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user