Last active
October 23, 2017 02:10
-
-
Save bboyle1234/9f9b05f74edd5739174ecaf80386e1a5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using ApexInvesting.Platform.Utilities; | |
using ApexTough.Providers; | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace ApexTough.TradingPlatform.Utilities { | |
/// <summary> | |
/// Throw this exception to indicate that NT8's operating timezone has changed, but the stored historical data has not been updated to match the new timezone. | |
/// Corrupted data will be displayed if the user proceeds. | |
/// </summary> | |
public class DataCorruptedByTimeZoneChangeException : Exception { | |
public DataCorruptedByTimeZoneChangeException(TimeZoneInfo previousTimeZone, TimeZoneInfo currentTimeZone) | |
: base($"The NT8 operating timezone has changed to '{currentTimeZone.DisplayName}' but the NT8 data cache has not been cleared. Either restore the NT8 operating timezone to '{previousTimeZone.DisplayName}' or clear the NT8 data cache.") { } | |
} | |
/// <summary> | |
/// Use this class to protect your users from inadvertently corrupting their historical data when they change the NT8 operating timezone. | |
/// The NT8 operating timezone can be set by the user from the Tools -> Options -> General -> TimeZone option, but it's very important to know | |
/// that users can unwittingly change the NT8 operating timezone by changing the timezone on their computer system clock. | |
/// </summary> | |
public static class DataCorruptionByTimeZoneChangeProtection { | |
private const string DATA_FILE_NAME = "NT8/TimeZoneChangeProtectionData"; | |
private static bool hasDetectedIssues = false; | |
/// <summary> | |
/// Throws an exception if data has been corrupted by the user changing NT8's operating timezone. | |
/// Call this method during toolkit bootstrap. | |
/// </summary> | |
public static void CheckForCorruption() { | |
try { | |
var savedData = FileSystemDataCache.Read<StoredData>(DATA_FILE_NAME); | |
var currentTimeZoneId = NinjaTrader.Core.Globals.GeneralOptions.TimeZoneInfo.Id; | |
/// If the toolkit is running for the first time, exit without error. | |
if (null == savedData) return; | |
/// If the timezone has not changed since last startup, exit without error. | |
if (currentTimeZoneId == savedData.TimeZoneId) return; | |
/// If the historical tick data cache has been cleared (files deleted), exit without error. | |
if (!File.Exists(savedData.DataFileName)) return; | |
/// If the historical tick data file has been modified, we know that it has most likely been updated for the new timezone, | |
/// exit without error. | |
if (GetHash(File.ReadAllBytes(savedData.DataFileName)) != savedData.DataFileHash) return; | |
/// We have detected data corruption | |
hasDetectedIssues = true; | |
throw new DataCorruptedByTimeZoneChangeException(TimeZoneInfo.FindSystemTimeZoneById(savedData.TimeZoneId), NinjaTrader.Core.Globals.GeneralOptions.TimeZoneInfo); | |
} finally { | |
TimerUtility.Callback(TimeSpan.FromSeconds(10), SaveState); | |
} | |
} | |
/// <summary> | |
/// Stores data that will be used for corruption checking next time NT8 starts up. | |
/// </summary> | |
private static void SaveState() { | |
if (hasDetectedIssues) { | |
/// If data corruption issues were detected on system startup, don't wanna save any new data until the user has resolved the original issue. | |
/// The user has already been instructed to either change the NT8 timezone back to what it was, or clear the data cache. If the user has reverted | |
/// or intends to revert the NT8 timezone setting, we would screw up the user's recovery process by overwriting state. | |
return; | |
} | |
/// Save system state ready for checking on next startup. | |
var data = new StoredData(); | |
data.TimeZoneId = NinjaTrader.Core.Globals.GeneralOptions.TimeZoneInfo.Id; | |
data.DataFileName = GetAHistoricalTickDataFileAndHash(out data.DataFileHash); | |
FileSystemDataCache.Save(DATA_FILE_NAME, data); | |
// If there were no historical data files available, we'll keep retrying until we find that one has been created. | |
if (null == data.DataFileName) | |
TimerUtility.Callback(TimeSpan.FromSeconds(10), SaveState); | |
} | |
/// <summary> | |
/// Chooses a filename from the historical tick data folder, ensuring that the chosen file has a length > 0 and calculates a hash for the file content. | |
/// </summary> | |
private static string GetAHistoricalTickDataFileAndHash(out int fileHash) { | |
var baseDirectory = new DirectoryInfo(Path.Combine(NinjaTrader.Core.Globals.UserDataDir, "db", "tick")); | |
foreach (var instrumentFolder in baseDirectory.GetDirectories()) { | |
foreach (var fileInfo in instrumentFolder.GetFiles("*.ncd")) { | |
var bytes = File.ReadAllBytes(fileInfo.FullName); | |
if (bytes.Length > 0) { | |
fileHash = GetHash(bytes); | |
return fileInfo.FullName; | |
} | |
} | |
} | |
fileHash = 0; | |
return null; | |
} | |
/// <summary> | |
/// Assumes that bytes.Length > 0 | |
/// </summary> | |
private static int GetHash(byte[] bytes) { | |
/// Assume that a lot of care has been put into making the string hashing function very efficient and unique. | |
return ASCIIEncoding.UTF8.GetString(bytes).GetHashCode(); | |
} | |
private class StoredData { | |
public string TimeZoneId; | |
public string DataFileName; | |
public int DataFileHash; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment