Last active
August 29, 2015 14:17
-
-
Save bboyle1234/12c3863f0179b26d9d5c to your computer and use it in GitHub Desktop.
TickDataContext_02
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
#region class TickDataProvider | |
public static class TickDataProvider { | |
static readonly string default247 = "Default 24/7"; | |
/// <summary> | |
/// Synchronization locks for accessing dataseries entries | |
/// </summary> | |
static readonly Dictionary<string, object> entryLocks = new Dictionary<string, object>(); | |
/// <summary> | |
/// All the dataseries entries | |
/// </summary> | |
static readonly Dictionary<string, TickDataSupply> entries = new Dictionary<string, TickDataSupply>(); | |
/// <summary> | |
/// Gets an entry synchronization lock object for the given instrument id. | |
/// Creates the lock object if it is not already stored in the EntryLocks dictionary. | |
/// </summary> | |
static object GetEntryLock(string instrumentId) { | |
// a bit of syntactical magic using IDictionary extension methods to create the object | |
// if it doesn't already exist in the dictionary | |
return entryLocks.GetWithConstructor(instrumentId, (key) => new object()); | |
} | |
/// <summary> | |
/// Use this method to get a tick data context. | |
/// </summary> | |
public static ITickDataSupply GetTickDataSupply(NinjaTraderInstrument instrument) { | |
lock (GetEntryLock(instrument.Id)) { | |
TickDataSupply entry; | |
if (!entries.TryGetValue(instrument.Id, out entry)) { | |
entry = new TickDataSupply(instrument); | |
entry.Error += OnEntryError; | |
entries[instrument.Id] = entry; | |
} | |
return entry; | |
} | |
} | |
static void OnEntryError(ITickDataSupply sender) { | |
lock (GetEntryLock((sender as TickDataSupply).NinjaTraderInstrument.Id)) { | |
// no need to dispose the entry object because it disposes itself | |
// immediately after raising this event | |
entries.Remove((sender as TickDataSupply).NinjaTraderInstrument.Id); | |
} | |
} | |
#region class TickDataContext | |
/// <summary> | |
/// Contains all related data for a given instance of 1-tick dataseries stored in TickDataProvider | |
/// </summary> | |
class TickDataSupply : ITickDataSupply, IDisposable { | |
/// <summary> | |
/// Fires when a dataseries has been obtained, just before processing begins. | |
/// Note that at the time this event fires, the first ticks have not yet been added | |
/// to the ticks list. | |
/// </summary> | |
public event Action<ITickDataSupply> Ready; | |
/// <summary> | |
/// Fires when a error puts this object into an unusable state | |
/// </summary> | |
public event Action<ITickDataSupply> Error; | |
// static readonly variables | |
/// <summary> | |
/// The start time of all dataseries to be loaded. | |
/// Used when calling NinjaTrader's Bars.GetBars method. | |
/// </summary> | |
static readonly DateTime from = DateTime.Now.Date.AddDays(-120); | |
/// <summary> | |
/// The to time of all dataseries to be loaded | |
/// Used when calling NinjaTrader's Bars.GetBars method. | |
/// </summary> | |
static readonly DateTime to = DateTime.Now.Date.AddDays(10); | |
/// <summary> | |
/// The period of all dataseries to be loaded | |
/// Used when calling NinjaTrader's Bars.GetBars method. | |
/// </summary> | |
static readonly NinjaTraderPeriod period = new NinjaTraderPeriod(NinjaTraderPeriodType.Tick, 1, NinjaTraderMarketDataType.Last); | |
/// <summary> | |
/// The session of all dataseries to be loaded | |
/// Used when calling NinjaTrader's Bars.GetBars method. | |
/// </summary> | |
static readonly NinjaTraderSession session = NinjaTraderSession.String2Session(default247); | |
// public readonly variables | |
/// <summary> | |
/// The NinjaTrader instrument that we will be obtaining tick data for | |
/// </summary> | |
public readonly NinjaTraderInstrument NinjaTraderInstrument; | |
/// <summary> | |
/// The Tough framework instrument that we will be obtaining tick data for | |
/// </summary> | |
public Instrument Instrument { get; private set; } | |
// private variables | |
/// <summary> | |
/// A reference to the NinjaTrader 1-tick bar series object that will be used to supply ticks | |
/// </summary> | |
NinjaTraderBars Bars; | |
/// <summary> | |
/// Populating this list is the main point of this object | |
/// This list is storage for the ticks we have read out of NinjaTrader. | |
/// </summary> | |
readonly List<Tick> Ticks; | |
/// <summary> | |
/// The thread that is used to continually read ticks out of the ninjatrader bars series | |
/// into a List of SolidTicks | |
/// </summary> | |
readonly Thread BarsToListPumpThread; | |
public TickDataSupply(NinjaTraderInstrument ninjaTraderInstrument) { | |
NinjaTraderInstrument = ninjaTraderInstrument; | |
Instrument = ninjaTraderInstrument.ToTough(); | |
Ticks = new List<Tick>(); | |
State = TickDataSupplyStates.Loading; | |
BarsToListPumpThread = new Thread(RunTickPump); | |
BarsToListPumpThread.IsBackground = true; | |
BarsToListPumpThread.Start(); | |
} | |
/// <summary> | |
/// Creates and returns an object that can be used to read the sequential | |
/// list of ticks that have been obtained. | |
/// Use this method whenever you need to start reading the list of ticks from the beginning. | |
/// </summary> | |
public TickDataSupplyReader CreateNewReader() { | |
return new TickDataSupplyReader(Ticks); | |
} | |
/// <summary> | |
/// Main method for the tick pumping thread. | |
/// This method loads the ninjatrader bars and pumps them into a tick list. | |
/// </summary> | |
void RunTickPump() { | |
try { | |
GetBars(); | |
PumpTicks(); | |
} catch (ThreadAbortException x) { // don't do OnError if the thread is simply aborting due to Disposal | |
} catch (Exception x) { | |
OnError(x); | |
} | |
} | |
void GetBars() { | |
// create a hook so that this object goes into error state | |
// if the bars are invalidated by a reload event | |
NinjaTraderBars.BarsReload += Bars_BarsReload; | |
// actually get the bars ... this can take a while | |
Bars = NinjaTraderBars.GetBars(NinjaTraderInstrument, period, from, to, session, true, true); | |
} | |
void PumpTicks() { | |
while (!IsDisposed) { | |
// store this variable so later we can check if we actually did read ticks | |
// from the ninjatrader bars | |
var originalCount = Ticks.Count; | |
while (Ticks.Count < Bars.Count) { | |
// sometimes the ninjatrader bars has storage added (increasing the bars.Count property) before the bar is | |
// actually ready to be processed. If we try to read the new bar while it's in this state, NinjaTrader | |
// will throw an exception. If this happens, we need to understand that NinjaTrader needs a little more time | |
// to get the new bar into a state where it can be read. Therefore we need to stop trying to read right up | |
// to bars.Count-1 | |
try { | |
// get data out of the bar object and store it in a SolidTick object | |
Ticks.Add(new Tick { Price = Bars.GetClose(Ticks.Count), Volume = Bars.GetVolume(Ticks.Count), TimestampLocal = Bars.GetTime(Ticks.Count) }); | |
} catch { | |
// NinjaTrader threw an exception indicating that the bar is not yet ready for us to read it | |
break; | |
} | |
} | |
// if ticks were actually read from the ninjatrader bars, we need to set some | |
// additional status properties | |
if (Ticks.Count > originalCount) { | |
TimeOfLastUpdate = DateTime.Now; | |
TimestampOfLastTickLocal = Ticks[Ticks.Count - 1].TimestampLocal; | |
// We have finished reading ticks to the end of what has been provided by ninjatrader. | |
// If this has just occurred for the first time, we have reached real-time processing, | |
// so we switch state from Loading to Ready | |
// Note that this block of code is situated inside the above if statement so that | |
// it is only executed AFTER ticks have actually been read. This is because this while loop | |
// starts executing before NinjaTrader provides bars, | |
if (State == TickDataSupplyStates.Loading) { | |
State = TickDataSupplyStates.Ready; | |
Ready.TryInvoke(this); | |
} | |
} | |
// rest and give more ticks time to arrive | |
Thread.Sleep(50); | |
} | |
} | |
/// <summary> | |
/// Called when the bars being processed are invalidated by a bars reloading event. | |
/// Causes this object to go into an error state | |
/// </summary> | |
void Bars_BarsReload(object sender, BarsReloadEventArgs e) { | |
if (e.Instrument == NinjaTraderInstrument) { | |
OnError(new BarsWereReloadedException()); | |
} | |
} | |
/// <summary> | |
/// Called when some sort of error causes this object to become unusable | |
/// </summary> | |
void OnError(Exception x) { | |
// set some status properties | |
LastError = x; | |
State = TickDataSupplyStates.Error; | |
// hopefully whoever listens to this event won't take forever to finish | |
Error.TryInvoke(this); | |
// finish up | |
Dispose(); | |
} | |
public TickDataSupplyStates State { | |
get; | |
private set; | |
} | |
public DateTime TimestampOfLastTickLocal { | |
get; | |
private set; | |
} | |
public DateTime TimeOfLastUpdate { | |
get; | |
private set; | |
} | |
public TimeSpan TimeSinceLastUpdate { | |
get { return DateTime.Now.Subtract(TimeOfLastUpdate); } | |
} | |
public int NumUnprocessedTicks { | |
get { | |
if (null != LastError) | |
return 0; | |
if (null == Bars) | |
return 0; | |
return Bars.Count - Ticks.Count; | |
} | |
} | |
public double ProcessingCompletionRatio { | |
get { | |
if (null != LastError) | |
return 0; | |
if (null == Bars || Bars.Count <= 1) | |
return 0; | |
return (double)Ticks.Count / Bars.Count; | |
} | |
} | |
public Exception LastError { | |
get; | |
private set; | |
} | |
public bool IsDisposed { get; private set; } | |
public void Dispose() { | |
if (!IsDisposed) { | |
IsDisposed = true; | |
NinjaTraderBars.BarsReload -= Bars_BarsReload; | |
BarsToListPumpThread.Abort(); | |
if (null != Bars) { | |
Bars.Dispose(); | |
} | |
} | |
} | |
} | |
#endregion | |
} | |
#endregion | |
#region class BarsWereReloadedException | |
/// <summary> | |
/// Exception indicatores that NinjaTrader rasied the Bars.BarsReload event while the | |
/// TickDataContext was operating normally. | |
/// </summary> | |
class BarsWereReloadedException : Exception { | |
public BarsWereReloadedException() : base("Bars were reloaded.") { } | |
} | |
#endregion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment