Skip to content

Instantly share code, notes, and snippets.

@bboyle1234
Last active October 23, 2017 02:10
Show Gist options
  • Save bboyle1234/9f9b05f74edd5739174ecaf80386e1a5 to your computer and use it in GitHub Desktop.
Save bboyle1234/9f9b05f74edd5739174ecaf80386e1a5 to your computer and use it in GitHub Desktop.
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