Skip to content

Instantly share code, notes, and snippets.

@bboyle1234
Last active August 29, 2015 14:17
Show Gist options
  • Save bboyle1234/12c3863f0179b26d9d5c to your computer and use it in GitHub Desktop.
Save bboyle1234/12c3863f0179b26d9d5c to your computer and use it in GitHub Desktop.
TickDataContext_02
#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