Unit CsvFileData; Interface Uses GenericDataNodes, DataNodes, TwoDLookup, Timers, Classes, SyncObjs; Type TFileOwnerDataNode = Class(TDataNodeWithStringKey) Private FFileName : String; Values : TTwoDArray; CriticalSection : TCriticalSection; Constructor Create(FileName : String); Procedure LoadData; Procedure FileChanged; Private LinkToSelf : TDataNodeLink; RecentlyUsed : Boolean; Timer : ITimerEvent; Procedure OnTimer; Protected Class Function CreateNew(Data : String) : TDataNodeWithStringKey; Override; Public Class Procedure Find(FileName : String; OnChange : TThreadMethod; Out Node : TFileOwnerDataNode; Out Link : TDataNodeLink); Function Get(ColHeader, RowHeader : String) : String; // This is safe only in the data node thread. Function ContainsRow(RowHeader : String) : Boolean; // This is safe only in the data node thread. Function ThreadSafeGet(ColHeader, RowHeader : String) : String; Destructor Destroy; Override; End; Function MakeDoubleCsvFactory(FileName, ColKey : String) : IGenericDataNodeFactory; Function MakeIntegerCsvFactory(FileName, ColKey : String) : IGenericDataNodeFactory; Function MakeStringCsvFactory(FileName, ColKey : String) : IGenericDataNodeFactory; Function GetVolumeBreak(Symbol : String) : Integer; Function GetAverageDailyVolume(Symbol : String) : Integer; Implementation Uses StandardPlaceHolders, SysUtils, Windows; //////////////////////////////////////////////////////////////////////// // TFileChangedDataNode //////////////////////////////////////////////////////////////////////// Type TFileChangedThread = Class; TFileChangedDataNode = Class(TDataNodeWithStringKey) Private FFileName : String; Thread : TFileChangedThread; ShutDownEvent, DirectoryListen : THandle; FileTimeStamp : Integer; Constructor Create(FileName : String); Procedure DirectoryChanged; Protected Class Function CreateNew(Data : String) : TDataNodeWithStringKey; Override; Public Class Procedure Find(FileName : String; OnChange : TThreadMethod; Out Link : TDataNodeLink); Destructor Destroy; Override; End; TFileChangedThread = Class(TThread) Private Owner : TFileChangedDataNode; Protected Procedure Execute; Override; End; Procedure TFileChangedThread.Execute; Var AllHandles : Array [0..2] Of THandle; Begin AllHandles[0] := Owner.ShutDownEvent; AllHandles[1] := Owner.DirectoryListen; AllHandles[2] := 0; Repeat WaitForMultipleObjects(2, @AllHandles, false, 600000); //WaitForSingleObject(Owner.DirectoryListen, MaxInt); If Terminated Then Break; Owner.AddToThreadQueue(Owner.DirectoryChanged); WaitForSingleObject(Owner.ShutDownEvent, 100) Until Terminated End; Procedure TFileChangedDataNode.DirectoryChanged; Var PrevTimeStamp : Integer; Begin PrevTimeStamp := FileTimeStamp; FileTimeStamp := FileAge(FFileName); If (PrevTimeStamp <> FileTimeStamp) And (FileTimeStamp <> -1) Then NotifyListeners; FindNextChangeNotification(DirectoryListen) End; Constructor TFileChangedDataNode.Create(FileName : String); Begin Inherited Create; FFileName := ExpandFileName(FileName); FileTimeStamp := FileAge(FFileName); ShutDownEvent := CreateEvent(Nil, True, False, ''); DirectoryListen := FindFirstChangeNotification( PChar(ExtractFilePath(FFileName)), False, FILE_NOTIFY_CHANGE_FILE_NAME Or FILE_NOTIFY_CHANGE_LAST_WRITE); If DirectoryListen <> INVALID_HANDLE_VALUE Then Begin Thread := TFileChangedThread.Create(True); Thread.Owner := Self; Thread.Resume End End; Class Function TFileChangedDataNode.CreateNew(Data : String) : TDataNodeWithStringKey; Begin Result := Create(Data) End; Destructor TFileChangedDataNode.Destroy; Begin If Assigned(Thread) Then Begin Thread.Terminate; SetEvent(ShutDownEvent); Thread.WaitFor; Thread.Free End; CloseHandle(ShutDownEvent); If DirectoryListen <> INVALID_HANDLE_VALUE Then FindCloseChangeNotification(DirectoryListen); Inherited End; Class Procedure TFileChangedDataNode.Find( FileName : String; OnChange : TThreadMethod; Out Link : TDataNodeLink); Var TempNode : TDataNodeWithStringKey; Begin FindCommon(TFileChangedDataNode, FileName, OnChange, TempNode, Link) End; //////////////////////////////////////////////////////////////////////// // TFileOwnerDataNode //////////////////////////////////////////////////////////////////////// Function TFileOwnerDataNode.ThreadSafeGet(ColHeader, RowHeader : String) : String; Begin CriticalSection.Enter; Try Result := Get(ColHeader, RowHeader) Finally CriticalSection.Leave End End; Function TFileOwnerDataNode.ContainsRow(RowHeader : String) : Boolean; Begin Result := Values.ContainsRow(RowHeader) End; Constructor TFileOwnerDataNode.Create(FileName : String); Var Link : TDataNodeLink; Begin Inherited Create; CriticalSection := TCriticalSection.Create; If ExtractFilePath(FileName) = '' Then FFileName := ExtractFilePath(ParamStr(0)) + FileName Else FFileName := FileName; Values := TTwoDArray.Create; LoadData; TFileChangedDataNode.Find(FFileName, FileChanged, Link); AddAutoLink(Link); LinkToSelf := CreateLink(Nil); RecentlyUsed := True; Timer := RequestPeriodicCallback(OnTimer, 60000) End; Procedure TFileOwnerDataNode.OnTimer; Begin If RecentlyUsed Then RecentlyUsed := False Else If Assigned(Timer) Then Begin Timer.Clear; Timer := Nil; LinkToSelf.Release // It might be nice to have something to see in the debugger, // but the datanode might be gone by this line, so we can't // do anything at this time. //LinkToSelf := Nil; End End; Procedure TFileOwnerDataNode.LoadData; Begin CriticalSection.Enter; Try Values.Clear; Try Values.LoadFromCSV(FFileName); Except On E : Exception Do Values.Clear End Finally CriticalSection.Leave End End; Procedure TFileOwnerDataNode.FileChanged; Begin LoadData; NotifyListeners End; Class Function TFileOwnerDataNode.CreateNew(Data : String) : TDataNodeWithStringKey; Begin Result := Create(Data) End; Destructor TFileOwnerDataNode.Destroy; Begin Values.Free; CriticalSection.Free; Inherited End; Class Procedure TFileOwnerDataNode.Find(FileName : String; OnChange : TThreadMethod; Out Node : TFileOwnerDataNode; Out Link : TDataNodeLink); Var TempNode : TDataNodeWithStringKey; Begin FindCommon(TFileOwnerDataNode, FileName, OnChange, TempNode, Link); Node := TempNode As TFileOwnerDataNode; Node.RecentlyUsed := True End; Function TFileOwnerDataNode.Get(ColHeader, RowHeader : String) : String; Begin Result := Values.Get(ColHeader, RowHeader) End; //////////////////////////////////////////////////////////////////////// // TFileDataNode //////////////////////////////////////////////////////////////////////// Type TFileDataNode = Class(TGenericDataNode) Private Data : TFileOwnerDataNode; ColKey, RowKey : String; Protected Procedure NewDataRecieved; Virtual; Function RawValue : String; Constructor Create(Params : TParamList); Override; Public End; Procedure TFileDataNode.NewDataRecieved; Begin NotifyListeners End; Constructor TFileDataNode.Create(Params : TParamList); Var Link : TDataNodeLink; Begin Inherited Create; Assert(Length(Params) = 3, 'Expected params: (FileName, ColKey, RowKey)'); TFileOwnerDataNode.Find(Params[0], NewDataRecieved, Data, Link); AddAutoLink(Link); ColKey := Params[1]; RowKey := Params[2] End; Function TFileDataNode.RawValue : String; Begin Result := Data.Get(ColKey, RowKey) End; //////////////////////////////////////////////////////////////////////// // TStringFileDataNode //////////////////////////////////////////////////////////////////////// Type TStringFileDataNode = Class(TFileDataNode) Protected Public Function IsValid : Boolean; Override; Published Function GetString : String; Override; End; Function TStringFileDataNode.IsValid : Boolean; Begin Result := True End; Function TStringFileDataNode.GetString : String; Begin Result := RawValue End; //////////////////////////////////////////////////////////////////////// // TDoubleFileDataNode //////////////////////////////////////////////////////////////////////// Type TDoubleFileDataNode = Class(TFileDataNode) Private CacheIsValid : Boolean; CachedValid : Boolean; CachedValue : Double; Procedure MakeCacheValid; Protected Procedure NewDataRecieved; Override; Public Function IsValid : Boolean; Override; Published Function GetDouble : Double; Override; End; Procedure TDoubleFileDataNode.MakeCacheValid; Begin If Not CacheIsValid Then Begin CachedValid := True; Try CachedValue := StrToFloat(RawValue) Except On Exception Do CachedValid := False End; CacheIsValid := True End End; Procedure TDoubleFileDataNode.NewDataRecieved; Begin CacheIsValid := False; Inherited End; Function TDoubleFileDataNode.IsValid : Boolean; Begin MakeCacheValid; Result := CachedValid End; Function TDoubleFileDataNode.GetDouble : Double; Begin MakeCacheValid; Result := CachedValue End; //////////////////////////////////////////////////////////////////////// // TIntegerFileDataNode //////////////////////////////////////////////////////////////////////// Type TIntegerFileDataNode = Class(TFileDataNode) Private CacheIsValid : Boolean; CachedValid : Boolean; CachedValue : Integer; Procedure MakeCacheValid; Protected Procedure NewDataRecieved; Override; Public Function IsValid : Boolean; Override; Published Function GetInteger : Integer; Override; End; Procedure TIntegerFileDataNode.MakeCacheValid; Begin If Not CacheIsValid Then Begin CachedValid := True; Try CachedValue := StrToInt(RawValue) Except On Exception Do CachedValid := False End; CacheIsValid := True End End; Procedure TIntegerFileDataNode.NewDataRecieved; Begin CacheIsValid := False; Inherited End; Function TIntegerFileDataNode.IsValid : Boolean; Begin MakeCacheValid; Result := CachedValid End; Function TIntegerFileDataNode.GetInteger : Integer; Begin MakeCacheValid; Result := CachedValue End; //////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////// Function MakeDoubleCsvFactory(FileName, ColKey : String) : IGenericDataNodeFactory; Begin Result := TGenericDataNodeFactory.CreateWithArgs(TDoubleFileDataNode, FileName, ColKey, StandardSymbolPlaceHolder) End; Function MakeIntegerCsvFactory(FileName, ColKey : String) : IGenericDataNodeFactory; Begin Result := TGenericDataNodeFactory.CreateWithArgs(TIntegerFileDataNode, FileName, ColKey, StandardSymbolPlaceHolder) End; Function MakeStringCsvFactory(FileName, ColKey : String) : IGenericDataNodeFactory; Begin Result := TGenericDataNodeFactory.CreateWithArgs(TStringFileDataNode, FileName, ColKey, StandardSymbolPlaceHolder) End; Function GetVolumeBreak(Symbol : String) : Integer; Var Data : TFileOwnerDataNode; Link : TDataNodeLink; Begin TFileOwnerDataNode.Find('OvernightData.csv', Nil, Data, Link); Result := StrToIntDef(Data.ThreadSafeGet('Volume Break', Symbol), 0); Link.Release End; Function GetAverageDailyVolume(Symbol : String) : Integer; Var Data : TFileOwnerDataNode; Link : TDataNodeLink; Begin TFileOwnerDataNode.Find('OvernightData.csv', Nil, Data, Link); Result := StrToIntDef(Data.ThreadSafeGet('Avg Daily Volume', Symbol), 0); Link.Release End; //////////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////////// Initialization TGenericDataNodeFactory.StoreFactory('VolumeBreak', MakeIntegerCsvFactory('OvernightData.csv', 'Volume Break')); TGenericDataNodeFactory.StoreFactory('ADVol', MakeIntegerCsvFactory('OvernightData.csv', 'Avg Daily Volume')); TGenericDataNodeFactory.StoreFactory('TickVolatility', MakeDoubleCsvFactory('OvernightData.csv', 'Tick Volatility')); TGenericDataNodeFactory.StoreFactory('UpDays', MakeIntegerCsvFactory('TC_OvernightData.csv', 'Up Days')); TGenericDataNodeFactory.StoreFactory('CorrelationSymbol', MakeStringCsvFactory('OvernightData.csv', 'Correlation Symbol')); TGenericDataNodeFactory.StoreFactory('CorrelationM', MakeDoubleCsvFactory('OvernightData.csv', 'Correlation M')); TGenericDataNodeFactory.StoreFactory('FCorrelationM', MakeDoubleCsvFactory('OvernightData.csv', 'F Correlation M')); TGenericDataNodeFactory.StoreFactory('RangeContraction', MakeIntegerCsvFactory('TC_OvernightData.csv', 'Range Contraction')); TGenericDataNodeFactory.StoreFactory('SMA200', MakeDoubleCsvFactory('TC_OvernightData.csv', '200 Day SMA')); TGenericDataNodeFactory.StoreFactory('SMA50', MakeDoubleCsvFactory('TC_OvernightData.csv', '50 Day SMA')); TGenericDataNodeFactory.StoreFactory('SMA20', MakeDoubleCsvFactory('TC_OvernightData.csv', '20 Day SMA')); TGenericDataNodeFactory.StoreFactory('BrightVolatility', MakeDoubleCsvFactory('OvernightData.csv', 'Bright Volatility')); TGenericDataNodeFactory.StoreFactory('SharesPerPrint', MakeDoubleCsvFactory('OvernightData.csv', 'Shares per Print')); End.