AuraTrader Sample
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
program AuraTrader;
|
||||||
|
|
||||||
|
uses
|
||||||
|
FastMM5,
|
||||||
|
System.StartUpCopy,
|
||||||
|
FMX.Forms,
|
||||||
|
MainForm in 'MainForm.pas' {Form1};
|
||||||
|
|
||||||
|
{$R *.res}
|
||||||
|
|
||||||
|
begin
|
||||||
|
Application.Initialize;
|
||||||
|
Application.CreateForm(TForm1, Form1);
|
||||||
|
Application.Run;
|
||||||
|
end.
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,224 @@
|
|||||||
|
<map version="freeplane 1.12.1">
|
||||||
|
<!--To view this file, download free mind mapping software Freeplane from https://www.freeplane.org -->
|
||||||
|
<node TEXT="AuraTrader" LOCALIZED_STYLE_REF="AutomaticLayout.level.root" FOLDED="false" ID="ID_1090958577" CREATED="1409300609620" MODIFIED="1749137964610" VGAP_QUANTITY="3 pt"><hook NAME="MapStyle" background="#d6e8e8ff" zoom="0.826">
|
||||||
|
<properties show_icon_for_attributes="true" edgeColorConfiguration="#808080ff,#ff0000ff,#0000ffff,#00ff00ff,#ff00ffff,#00ffffff,#7c0000ff,#00007cff,#007c00ff,#7c007cff,#007c7cff,#7c7c00ff" show_tags="UNDER_NODES" show_note_icons="true" associatedTemplateLocation="template:/light_sky_element_template.mm" fit_to_viewport="false" show_icons="BESIDE_NODES" showTagCategories="false"/>
|
||||||
|
<tags category_separator="::"/>
|
||||||
|
|
||||||
|
<map_styles>
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.root_node" STYLE="oval" UNIFORM_SHAPE="true" VGAP_QUANTITY="24 pt">
|
||||||
|
<font SIZE="24"/>
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.predefined" POSITION="bottom_or_right" STYLE="bubble">
|
||||||
|
<stylenode LOCALIZED_TEXT="default" ID="ID_4172522" ICON_SIZE="12 pt" FORMAT_AS_HYPERLINK="false" COLOR="#051552" BACKGROUND_COLOR="#5cd5e8" STYLE="bubble" SHAPE_HORIZONTAL_MARGIN="8 pt" SHAPE_VERTICAL_MARGIN="5 pt" BORDER_WIDTH_LIKE_EDGE="false" BORDER_WIDTH="1.7 px" BORDER_COLOR_LIKE_EDGE="false" BORDER_COLOR="#1164b0" BORDER_DASH_LIKE_EDGE="true" BORDER_DASH="SOLID" VGAP_QUANTITY="3 pt">
|
||||||
|
<arrowlink SHAPE="CUBIC_CURVE" COLOR="#000000" WIDTH="2" TRANSPARENCY="200" DASH="" FONT_SIZE="9" FONT_FAMILY="SansSerif" DESTINATION="ID_4172522" STARTINCLINATION="116.25 pt;0 pt;" ENDINCLINATION="116.25 pt;28.5 pt;" STARTARROW="NONE" ENDARROW="DEFAULT"/>
|
||||||
|
<font NAME="SansSerif" SIZE="10" BOLD="false" STRIKETHROUGH="false" ITALIC="false"/>
|
||||||
|
<edge STYLE="bezier" COLOR="#051552" WIDTH="2" DASH="SOLID"/>
|
||||||
|
<richcontent TYPE="DETAILS" CONTENT-TYPE="plain/auto"/>
|
||||||
|
<richcontent TYPE="NOTE" CONTENT-TYPE="plain/auto"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="defaultstyle.details" COLOR="#fff024" BACKGROUND_COLOR="#000000"/>
|
||||||
|
<stylenode LOCALIZED_TEXT="defaultstyle.tags">
|
||||||
|
<font SIZE="10"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="defaultstyle.attributes">
|
||||||
|
<font SIZE="9"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="defaultstyle.note" COLOR="#000000" BACKGROUND_COLOR="#f6f9a1" TEXT_ALIGN="LEFT">
|
||||||
|
<icon BUILTIN="clock2"/>
|
||||||
|
<font SIZE="10" ITALIC="true"/>
|
||||||
|
<edge COLOR="#000000"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="defaultstyle.floating">
|
||||||
|
<edge STYLE="hide_edge"/>
|
||||||
|
<cloud COLOR="#f0f0f0" SHAPE="ROUND_RECT"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="defaultstyle.selection" COLOR="#ffffff" BACKGROUND_COLOR="#cc7212" BORDER_COLOR_LIKE_EDGE="false" BORDER_COLOR="#1164b0"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.user-defined" POSITION="bottom_or_right" STYLE="bubble">
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.important" ID="ID_1823054225" COLOR="#fff024" BORDER_COLOR_LIKE_EDGE="false" BORDER_COLOR="#9d5e19">
|
||||||
|
<icon BUILTIN="yes"/>
|
||||||
|
<arrowlink COLOR="#9d5e19" TRANSPARENCY="255" DESTINATION="ID_1823054225"/>
|
||||||
|
<font SIZE="11" BOLD="true"/>
|
||||||
|
<edge COLOR="#9d5e19"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.flower" COLOR="#ffffff" BACKGROUND_COLOR="#255aba" STYLE="oval" TEXT_ALIGN="CENTER" BORDER_WIDTH_LIKE_EDGE="false" BORDER_WIDTH="22 pt" BORDER_COLOR_LIKE_EDGE="false" BORDER_COLOR="#f9d71c" BORDER_DASH_LIKE_EDGE="false" BORDER_DASH="CLOSE_DOTS" MAX_WIDTH="6 cm" MIN_WIDTH="3 cm"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="styles.AutomaticLayout" POSITION="bottom_or_right" STYLE="bubble">
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level.root" COLOR="#ffffff" BACKGROUND_COLOR="#053d8b" STYLE="bubble" SHAPE_HORIZONTAL_MARGIN="10 pt" SHAPE_VERTICAL_MARGIN="10 pt" BORDER_COLOR_LIKE_EDGE="false" BORDER_COLOR="#2c2b29" BORDER_DASH_LIKE_EDGE="true">
|
||||||
|
<font SIZE="18"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,1" COLOR="#ffffff" BACKGROUND_COLOR="#1164b0" STYLE="bubble" SHAPE_HORIZONTAL_MARGIN="8 pt" SHAPE_VERTICAL_MARGIN="5 pt" BORDER_COLOR="#2c2b29">
|
||||||
|
<font SIZE="16"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,2" COLOR="#ffffff" BACKGROUND_COLOR="#298bc8" STYLE="bubble" SHAPE_HORIZONTAL_MARGIN="8 pt" SHAPE_VERTICAL_MARGIN="5 pt" BORDER_COLOR_LIKE_EDGE="true" BORDER_COLOR="#f0f0f0">
|
||||||
|
<font SIZE="14"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,3" COLOR="#ffffff" BACKGROUND_COLOR="#3fb7db" STYLE="bubble" SHAPE_HORIZONTAL_MARGIN="8 pt" SHAPE_VERTICAL_MARGIN="5 pt" BORDER_COLOR_LIKE_EDGE="true" BORDER_COLOR="#f0f0f0">
|
||||||
|
<font SIZE="12"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,4" COLOR="#051552" BACKGROUND_COLOR="#5cd5e8" BORDER_COLOR_LIKE_EDGE="true" BORDER_COLOR="#f0f0f0">
|
||||||
|
<font SIZE="11"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,5" BORDER_COLOR_LIKE_EDGE="true" BORDER_COLOR="#f0f0f0">
|
||||||
|
<font SIZE="11"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,6" BORDER_COLOR_LIKE_EDGE="true" BORDER_COLOR="#f0f0f0">
|
||||||
|
<font SIZE="10"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,7" BORDER_COLOR="#f0f0f0">
|
||||||
|
<font SIZE="10"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,8" BORDER_COLOR="#f0f0f0">
|
||||||
|
<font SIZE="10"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,9" BORDER_COLOR="#f0f0f0">
|
||||||
|
<font SIZE="10"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,10" BORDER_COLOR="#f0f0f0">
|
||||||
|
<font SIZE="9"/>
|
||||||
|
</stylenode>
|
||||||
|
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,11" BORDER_COLOR="#f0f0f0">
|
||||||
|
<font SIZE="9"/>
|
||||||
|
</stylenode>
|
||||||
|
</stylenode>
|
||||||
|
</stylenode>
|
||||||
|
</map_styles>
|
||||||
|
</hook>
|
||||||
|
<hook NAME="accessories/plugins/AutomaticLayout.properties" VALUE="ALL"/>
|
||||||
|
<font BOLD="true"/>
|
||||||
|
<node TEXT="Konzept" POSITION="bottom_or_right" ID="ID_351347589" CREATED="1749138140790" MODIFIED="1749138143372">
|
||||||
|
<node TEXT="Graph" ID="ID_1387256995" CREATED="1749138153896" MODIFIED="1749138171293">
|
||||||
|
<node TEXT="Node" ID="ID_259083893" CREATED="1749197003119" MODIFIED="1749197028434">
|
||||||
|
<node TEXT="Hat eine Signatur" POSITION="bottom_or_right" ID="ID_1518342419" CREATED="1749138332065" MODIFIED="1749198016791">
|
||||||
|
<node TEXT="Eingänge" ID="ID_225862940" CREATED="1749198017375" MODIFIED="1749198021478">
|
||||||
|
<node TEXT="Lazy<T>" ID="ID_1288547800" CREATED="1749198026579" MODIFIED="1749198040009"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Ausgänge" ID="ID_661812861" CREATED="1749198022539" MODIFIED="1749198024513">
|
||||||
|
<node TEXT="Mutable<T>" ID="ID_1005406898" CREATED="1749198042405" MODIFIED="1749198082198"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Caption" POSITION="bottom_or_right" ID="ID_1196798317" CREATED="1749197994222" MODIFIED="1749197997264"/>
|
||||||
|
<node TEXT="Grafische Repräsentation" POSITION="bottom_or_right" ID="ID_1686858497" CREATED="1749198609593" MODIFIED="1749198635728"/>
|
||||||
|
<node TEXT="Jeder Node ist eine Factory" POSITION="bottom_or_right" ID="ID_1175294085" CREATED="1749199661343" MODIFIED="1749199751197">
|
||||||
|
<node TEXT="Die eigentliche Komponente wird erst beim Init der Strategie erzeugt" ID="ID_691361700" CREATED="1749199752043" MODIFIED="1749199765349"/>
|
||||||
|
<node TEXT="So ist es möglich, dass z.B. ein Chart mit mehreren Eingängen verknüpft wird" ID="ID_1680171724" CREATED="1749199770731" MODIFIED="1749199814332"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="abstract templates" ID="ID_1144603697" CREATED="1749197934224" MODIFIED="1749197953583">
|
||||||
|
<node TEXT="DataServer" POSITION="bottom_or_right" ID="ID_291421820" CREATED="1749138179173" MODIFIED="1749202649741">
|
||||||
|
<node TEXT="Können Preisdaten aus beliebigen Quellen sammeln und stellen sie als Stream zur verfügung" ID="ID_827558062" CREATED="1749138204738" MODIFIED="1749138284430"/>
|
||||||
|
<node TEXT="Können sowohl Live- als auch historische Daten liefern" ID="ID_896603589" CREATED="1749193159987" MODIFIED="1749193216833">
|
||||||
|
<node TEXT="Für den Consumer darf das (eigentlich) nicht transparent sein" ID="ID_554512865" CREATED="1749193217206" MODIFIED="1749196862117"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Signatur" ID="ID_1267368219" CREATED="1749195431793" MODIFIED="1749195436151">
|
||||||
|
<node TEXT="Keine Eingänge!" POSITION="bottom_or_right" ID="ID_65784960" CREATED="1749200853295" MODIFIED="1749200862455">
|
||||||
|
<node TEXT="Registriert sich als IDataProvider und publiziert so seine Fetch-Routine" POSITION="bottom_or_right" ID="ID_351312947" CREATED="1749200902001" MODIFIED="1749200934037"/>
|
||||||
|
<node TEXT="Fetch - signalisiert, dass neue Daten abgerufen werden können" POSITION="bottom_or_right" ID="ID_1521536523" CREATED="1749193885863" MODIFIED="1749194845452">
|
||||||
|
<node TEXT="Live" ID="ID_1178415156" CREATED="1749196109094" MODIFIED="1749196123441">
|
||||||
|
<node TEXT="Asynchron Daten von der externen Quelle abrufen" ID="ID_26813979" CREATED="1749196129271" MODIFIED="1749201012068"/>
|
||||||
|
<node TEXT="Es werden nach einem Fetch immer alle bis dahin enfangenen Daten verarbeitet" ID="ID_1279384359" CREATED="1749196364669" MODIFIED="1749196388802"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Backtest" ID="ID_1462183661" CREATED="1749196123907" MODIFIED="1749196127688">
|
||||||
|
<node TEXT="Einlesen der historischen Daten aus lokaler Quelle" ID="ID_766072914" CREATED="1749196215155" MODIFIED="1749196299263"/>
|
||||||
|
<node TEXT="Wie viele Daten sollen hier gelesen werden?" ID="ID_955008466" CREATED="1749196316474" MODIFIED="1749196340955">
|
||||||
|
<node TEXT="Der Backtest könnte theoretisch alle Daten in einem Fetch liefern" ID="ID_783811796" CREATED="1749196423022" MODIFIED="1749196468608"/>
|
||||||
|
<node TEXT="Stattdessen sollen "sinnvolle" Häppchen geliefert werden" ID="ID_1420632589" CREATED="1749196470465" MODIFIED="1749196493915"/>
|
||||||
|
<node TEXT="Z.B. in dem die Timestamps der Daten analysiert werden." ID="ID_994363528" CREATED="1749196495078" MODIFIED="1749196523145">
|
||||||
|
<node TEXT="Ticks die sehr schnell nacheinender kommen, könnten zusammengefasst werden." ID="ID_262920625" CREATED="1749196523651" MODIFIED="1749196546182"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Ausgang" POSITION="bottom_or_right" ID="ID_354621746" CREATED="1749193952447" MODIFIED="1749193957977">
|
||||||
|
<node TEXT="Array" ID="ID_1992679898" CREATED="1749196558592" MODIFIED="1749196561177">
|
||||||
|
<node TEXT="Enthält alle Datenpunkte, die seit dem letzten Durchlauf empfangen wurden" POSITION="bottom_or_right" ID="ID_1560263964" CREATED="1749194019659" MODIFIED="1749196612914"/>
|
||||||
|
<node TEXT="Datenpunkt" ID="ID_1319987980" CREATED="1749196616408" MODIFIED="1749196620117">
|
||||||
|
<node TEXT="Index seit Start der Strategy" POSITION="bottom_or_right" ID="ID_1416903711" CREATED="1749194191668" MODIFIED="1749194208147"/>
|
||||||
|
<node TEXT="Index seit dem letzten Session Break (wenn die Datenquelle das unterstützt)" POSITION="bottom_or_right" ID="ID_1894209537" CREATED="1749194208989" MODIFIED="1749194250436"/>
|
||||||
|
<node TEXT="Daten" ID="ID_508077608" CREATED="1749196736643" MODIFIED="1749196745736">
|
||||||
|
<node TEXT="Timestamp" POSITION="bottom_or_right" ID="ID_1600370587" CREATED="1749195711666" MODIFIED="1749195717838"/>
|
||||||
|
<node TEXT="Volume" POSITION="bottom_or_right" ID="ID_719731480" CREATED="1749196709681" MODIFIED="1749196711684"/>
|
||||||
|
<node TEXT="abstrakt" POSITION="bottom_or_right" ID="ID_1867165552" CREATED="1749196643858" MODIFIED="1749196675549">
|
||||||
|
<node TEXT="OHLC" POSITION="bottom_or_right" ID="ID_400927533" CREATED="1749195379156" MODIFIED="1749196820207"/>
|
||||||
|
<node TEXT="Tick" POSITION="bottom_or_right" ID="ID_1133389846" CREATED="1749195375327" MODIFIED="1749196821669">
|
||||||
|
<node TEXT="Ask-Bid" POSITION="bottom_or_right" ID="ID_127401459" CREATED="1749194269527" MODIFIED="1749196722182"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="IsLiveData" ID="ID_1043765011" CREATED="1749196881074" MODIFIED="1749196892932"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Trader" POSITION="bottom_or_right" ID="ID_1544725450" CREATED="1749197183355" MODIFIED="1749197191427">
|
||||||
|
<node TEXT="Implementiert, wie mit Signalen umgegangen wird" ID="ID_102160159" CREATED="1749197357878" MODIFIED="1749197388131"/>
|
||||||
|
<node TEXT="Signatur" ID="ID_1668381928" CREATED="1749198186867" MODIFIED="1749198190179">
|
||||||
|
<node TEXT="Eingang" POSITION="bottom_or_right" ID="ID_967519634" CREATED="1749197096963" MODIFIED="1749197103083">
|
||||||
|
<node TEXT="Buy-Signal" ID="ID_1120078634" CREATED="1749197103978" MODIFIED="1749197398085"/>
|
||||||
|
<node TEXT="Sell-Signal" ID="ID_1074440473" CREATED="1749197140015" MODIFIED="1749197402441"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Ausgang" POSITION="bottom_or_right" ID="ID_1984669993" CREATED="1749197229378" MODIFIED="1749197231751">
|
||||||
|
<node TEXT="Orders" ID="ID_84017955" CREATED="1749197256250" MODIFIED="1749197267082"/>
|
||||||
|
<node TEXT="Positions" ID="ID_106757702" CREATED="1749197270413" MODIFIED="1749197273907">
|
||||||
|
<node TEXT="Deals" ID="ID_1873504915" CREATED="1749197303336" MODIFIED="1749197305157"/>
|
||||||
|
<node TEXT="IsOpen" ID="ID_111000501" CREATED="1749197318276" MODIFIED="1749197322652"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Indicator" ID="ID_1041206949" CREATED="1749198254012" MODIFIED="1749198258932">
|
||||||
|
<node TEXT="Frei programmierbare Module" ID="ID_1046851781" CREATED="1749198260161" MODIFIED="1749198292019"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Diagramme" ID="ID_1728273110" CREATED="1749198882498" MODIFIED="1749199486967">
|
||||||
|
<node TEXT="abstract" POSITION="bottom_or_right" ID="ID_1648724212" CREATED="1749198794903" MODIFIED="1749198800455">
|
||||||
|
<node TEXT="Tick-Chart" ID="ID_1017072631" CREATED="1749198800457" MODIFIED="1749198831938"/>
|
||||||
|
<node TEXT="OHLC-Chart" ID="ID_629656972" CREATED="1749198805040" MODIFIED="1749198837689"/>
|
||||||
|
<node TEXT="Histogram" ID="ID_34977684" CREATED="1749198841247" MODIFIED="1749199015989"/>
|
||||||
|
<node TEXT="Diagram" ID="ID_840651195" CREATED="1749199016656" MODIFIED="1749199108308"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Draw-Plane mit X/Y Achse" ID="ID_587693864" CREATED="1749198904253" MODIFIED="1749199081316"/>
|
||||||
|
<node TEXT="Charts" ID="ID_382545852" CREATED="1749199466839" MODIFIED="1749199491297">
|
||||||
|
<node TEXT="Eingang" POSITION="bottom_or_right" ID="ID_1587680882" CREATED="1749199129247" MODIFIED="1749199394732">
|
||||||
|
<node TEXT="Klar definierte Dateneingänge" ID="ID_548619408" CREATED="1749199135233" MODIFIED="1749199152222">
|
||||||
|
<node TEXT="z.B. TickData für Tick-Chart" ID="ID_383484888" CREATED="1749199152224" MODIFIED="1749199163416"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Eingänge dynamisch erzeugen." ID="ID_710689831" CREATED="1749199168396" MODIFIED="1749199223960">
|
||||||
|
<node TEXT="z.B. ein EMA auf einem OHLC-Chart" ID="ID_288893788" CREATED="1749199223961" MODIFIED="1749199254594">
|
||||||
|
<node TEXT="Ausgang des EMA liefert nur einen Wert" ID="ID_1879052051" CREATED="1749199255287" MODIFIED="1749199283462"/>
|
||||||
|
<node TEXT="Muss über einen im Chart-Node definierbaren Eingang verknüpft werden" ID="ID_1048634781" CREATED="1749199285281" MODIFIED="1749199324815">
|
||||||
|
<node TEXT="Definiert den Style des EMA" ID="ID_1401266298" CREATED="1749199343035" MODIFIED="1749199359978"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="AddChartInput()" ID="ID_851859706" CREATED="1749199909158" MODIFIED="1749200050028">
|
||||||
|
<node TEXT="Liefert einen neuen Eingang mit eigenen Style-Parametern" ID="ID_1326484717" CREATED="1749200057661" MODIFIED="1749200088602"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Ausgang" POSITION="bottom_or_right" ID="ID_1838167422" CREATED="1749199387596" MODIFIED="1749199390976">
|
||||||
|
<node TEXT="Sichtbarer Datenbereich" ID="ID_149531054" CREATED="1749199425386" MODIFIED="1749199530548">
|
||||||
|
<node TEXT="Damit können andere Diagramme ihren Sichtbereich an den Chart anpassen." ID="ID_21321688" CREATED="1749199531020" MODIFIED="1749199559573"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Last Value" ID="ID_179046743" CREATED="1749199567850" MODIFIED="1749199578133"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Histogram" ID="ID_1926471247" CREATED="1749199595197" MODIFIED="1749199599389"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Chart" ID="ID_1485297910" CREATED="1749198734818" MODIFIED="1749198738034">
|
||||||
|
<node TEXT="Tick" ID="ID_1989326003" CREATED="1749198752908" MODIFIED="1749198768413">
|
||||||
|
<node TEXT="" ID="ID_276822419" CREATED="1749198770321" MODIFIED="1749198770321"/>
|
||||||
|
</node>
|
||||||
|
<node TEXT="OHLC" ID="ID_1040780755" CREATED="1749198757224" MODIFIED="1749198762424"/>
|
||||||
|
<node TEXT="Eingang" ID="ID_1311557583" CREATED="1749198785094" MODIFIED="1749198793621"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
<node TEXT="Broker" POSITION="bottom_or_right" ID="ID_1666096793" CREATED="1749138364769" MODIFIED="1749138404806">
|
||||||
|
<node TEXT="Order- und Positionsmanagement" ID="ID_982862918" CREATED="1749138407782" MODIFIED="1749138443566"/>
|
||||||
|
<node TEXT="Reduziert auf das absolute Minimum" ID="ID_620096980" CREATED="1749138462663" MODIFIED="1749138472587"/>
|
||||||
|
<node TEXT="Plazieren von Limit- und Market-Orders" ID="ID_325256813" CREATED="1749138473278" MODIFIED="1749138498552"/>
|
||||||
|
<node TEXT="Verschiedene Ausführungsmodelle" ID="ID_1368393579" CREATED="1749138525992" MODIFIED="1749138537994"/>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</node>
|
||||||
|
</map>
|
||||||
Binary file not shown.
@@ -0,0 +1,68 @@
|
|||||||
|
object Form1: TForm1
|
||||||
|
Left = 0
|
||||||
|
Top = 0
|
||||||
|
Caption = 'Form1'
|
||||||
|
ClientHeight = 840
|
||||||
|
ClientWidth = 808
|
||||||
|
FormFactor.Width = 320
|
||||||
|
FormFactor.Height = 480
|
||||||
|
FormFactor.Devices = [Desktop]
|
||||||
|
OnCreate = FormCreate
|
||||||
|
OnDestroy = FormDestroy
|
||||||
|
DesignerMasterStyle = 0
|
||||||
|
object Panel1: TPanel
|
||||||
|
Align = Top
|
||||||
|
Size.Width = 808.00000000000000000
|
||||||
|
Size.Height = 529.00000000000000000
|
||||||
|
Size.PlatformDefault = False
|
||||||
|
TabOrder = 2
|
||||||
|
object RandomButton: TButton
|
||||||
|
Position.X = 697.00000000000000000
|
||||||
|
Position.Y = 38.00000000000000000
|
||||||
|
TabOrder = 0
|
||||||
|
Text = 'Random'
|
||||||
|
TextSettings.Trimming = None
|
||||||
|
OnClick = RandomButtonClick
|
||||||
|
end
|
||||||
|
object Path1: TPath
|
||||||
|
Anchors = [akLeft, akTop, akRight]
|
||||||
|
Position.X = 8.00000000000000000
|
||||||
|
Position.Y = 8.00000000000000000
|
||||||
|
Size.Width = 489.00000000000000000
|
||||||
|
Size.Height = 513.00000000000000000
|
||||||
|
Size.PlatformDefault = False
|
||||||
|
end
|
||||||
|
object SymbolsComboBox: TComboBox
|
||||||
|
Anchors = [akTop, akRight]
|
||||||
|
Position.X = 505.00000000000000000
|
||||||
|
Position.Y = 8.00000000000000000
|
||||||
|
Size.Width = 217.00000000000000000
|
||||||
|
Size.Height = 22.00000000000000000
|
||||||
|
Size.PlatformDefault = False
|
||||||
|
TabOrder = 2
|
||||||
|
end
|
||||||
|
object LoadButton: TButton
|
||||||
|
Anchors = [akTop, akRight]
|
||||||
|
Position.X = 730.00000000000000000
|
||||||
|
Position.Y = 8.00000000000000000
|
||||||
|
Size.Width = 47.00000000000000000
|
||||||
|
Size.Height = 22.00000000000000000
|
||||||
|
Size.PlatformDefault = False
|
||||||
|
TabOrder = 3
|
||||||
|
Text = 'Load'
|
||||||
|
TextSettings.Trimming = None
|
||||||
|
OnClick = LoadButtonClick
|
||||||
|
end
|
||||||
|
end
|
||||||
|
object LogMemo: TMemo
|
||||||
|
Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
|
||||||
|
DataDetectorTypes = []
|
||||||
|
Align = Client
|
||||||
|
Size.Width = 808.00000000000000000
|
||||||
|
Size.Height = 311.00000000000000000
|
||||||
|
Size.PlatformDefault = False
|
||||||
|
TabOrder = 1
|
||||||
|
Viewport.Width = 804.00000000000000000
|
||||||
|
Viewport.Height = 307.00000000000000000
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
unit MainForm;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
System.Types,
|
||||||
|
System.UITypes,
|
||||||
|
System.Classes,
|
||||||
|
System.Variants,
|
||||||
|
System.DateUtils,
|
||||||
|
FMX.Types,
|
||||||
|
FMX.Controls,
|
||||||
|
FMX.Forms,
|
||||||
|
FMX.Graphics,
|
||||||
|
FMX.Dialogs,
|
||||||
|
FMX.Controls.Presentation,
|
||||||
|
FMX.StdCtrls,
|
||||||
|
FMX.ListView.Types,
|
||||||
|
FMX.ListView.Appearances,
|
||||||
|
FMX.ListView.Adapters.Base,
|
||||||
|
FMX.ListView,
|
||||||
|
FMX.Memo.Types,
|
||||||
|
FMX.ScrollBox,
|
||||||
|
FMX.Memo,
|
||||||
|
FMX.Objects,
|
||||||
|
Myc.Futures,
|
||||||
|
Myc.Trade.DataStream,
|
||||||
|
Myc.Trade.DataPoint,
|
||||||
|
Myc.Signals,
|
||||||
|
Myc.Signals.FMX,
|
||||||
|
FMX.ListBox;
|
||||||
|
|
||||||
|
type
|
||||||
|
TForm1 = class(TForm)
|
||||||
|
LogMemo: TMemo;
|
||||||
|
Panel1: TPanel;
|
||||||
|
RandomButton: TButton;
|
||||||
|
Path1: TPath;
|
||||||
|
SymbolsComboBox: TComboBox;
|
||||||
|
LoadButton: TButton;
|
||||||
|
procedure RandomButtonClick(Sender: TObject);
|
||||||
|
procedure FormCreate(Sender: TObject);
|
||||||
|
procedure FormDestroy(Sender: TObject);
|
||||||
|
procedure LoadButtonClick(Sender: TObject);
|
||||||
|
private
|
||||||
|
FOnEvent: TNotifyEvent;
|
||||||
|
{ Private declarations }
|
||||||
|
FServer: IDataServer<TAskBidItem>;
|
||||||
|
FSymbolsValid: TFMXValidationState;
|
||||||
|
FStreamUpdate: TFMXValidationState;
|
||||||
|
FSymbols: TFuture<TArray<String>>;
|
||||||
|
FStream: IDataStream<TAskBidItem>;
|
||||||
|
FRandomStream: array[0..19] of IDataStream<TAskBidItem>;
|
||||||
|
FStreamIdx: Integer;
|
||||||
|
procedure DoIdle(Sender: TObject; var Done: Boolean);
|
||||||
|
public
|
||||||
|
{ Public declarations }
|
||||||
|
published
|
||||||
|
property OnEvent: TNotifyEvent read FOnEvent write FOnEvent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
var
|
||||||
|
Form1: TForm1;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
{$R *.fmx}
|
||||||
|
|
||||||
|
procedure TForm1.FormCreate(Sender: TObject);
|
||||||
|
begin
|
||||||
|
// Create an instance of the TAuraTABFileServer. The server can be reused for multiple stream creations. [364]
|
||||||
|
FServer := TAuraTABFileServer.Create('\\COFFEE\TickData\Pepperstone');
|
||||||
|
|
||||||
|
SymbolsComboBox.Enabled := false;
|
||||||
|
|
||||||
|
FSymbols := FServer.EnumerateSymbols;
|
||||||
|
|
||||||
|
FSymbolsValid :=
|
||||||
|
TFMXValidationState.Create(
|
||||||
|
SymbolsComboBox,
|
||||||
|
FSymbols.Done,
|
||||||
|
procedure
|
||||||
|
begin
|
||||||
|
SymbolsComboBox.BeginUpdate;
|
||||||
|
try
|
||||||
|
SymbolsComboBox.Items.Clear;
|
||||||
|
SymbolsComboBox.Items.AddStrings(FSymbols.WaitFor);
|
||||||
|
if SymbolsComboBox.Items.Count > 0 then
|
||||||
|
begin
|
||||||
|
if SymbolsComboBox.ItemIndex < 0 then
|
||||||
|
SymbolsComboBox.ItemIndex := SymbolsComboBox.Items.IndexOf('GER40');
|
||||||
|
SymbolsComboBox.Enabled := true;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
SymbolsComboBox.EndUpdate;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
);
|
||||||
|
|
||||||
|
Application.OnIdle := DoIdle;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TForm1.FormDestroy(Sender: TObject);
|
||||||
|
begin
|
||||||
|
Application.OnIdle := nil;
|
||||||
|
FSymbolsValid.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TForm1.LoadButtonClick(Sender: TObject);
|
||||||
|
begin
|
||||||
|
FStream := FServer.CreateStream(FSymbols.WaitFor[SymbolsComboBox.ItemIndex]);
|
||||||
|
FStreamUpdate := TFMXValidationState.Create(Path1, FStream.HasData,
|
||||||
|
procedure
|
||||||
|
var
|
||||||
|
Data: array [0..5] of TDataPoint<TAskBidItem>;
|
||||||
|
begin
|
||||||
|
var cnt := FStream.GetChunk(Data);
|
||||||
|
if cnt = 0 then exit;
|
||||||
|
|
||||||
|
for var i:=0 to cnt-1 do
|
||||||
|
Path1.Data.LineTo( PointF(Path1.Data.Count, Data[i].Data.Ask) );
|
||||||
|
end);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TForm1.RandomButtonClick(Sender: TObject);
|
||||||
|
begin
|
||||||
|
var ass := FSymbols.WaitFor;
|
||||||
|
FRandomStream[FStreamIdx] := FServer.CreateStream(ass[Random(Length(ass))]);
|
||||||
|
|
||||||
|
for var i := 0 to High(FRandomStream) do
|
||||||
|
if LogMemo.Lines.Count = i then
|
||||||
|
LogMemo.Lines.Add('');
|
||||||
|
LogMemo.Lines[FStreamIdx] := FRandomStream[FStreamIdx].Symbol + ' loading...';
|
||||||
|
|
||||||
|
FStreamIdx := (FStreamIdx + 1) mod Length(FRandomStream);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TForm1.DoIdle(Sender: TObject; var Done: Boolean);
|
||||||
|
var
|
||||||
|
Items: TArray<TDataPoint<TAskBidItem>>;
|
||||||
|
begin
|
||||||
|
SetLength(Items, 200);
|
||||||
|
|
||||||
|
LogMemo.BeginUpdate;
|
||||||
|
try
|
||||||
|
for var i := 0 to High(FRandomStream) do
|
||||||
|
begin
|
||||||
|
var str := '';
|
||||||
|
if LogMemo.Lines.Count > i then
|
||||||
|
str := LogMemo.Lines[i]
|
||||||
|
else
|
||||||
|
LogMemo.Lines.Add('');
|
||||||
|
|
||||||
|
if Assigned(FRandomStream[i]) then
|
||||||
|
begin
|
||||||
|
var cnt := FRandomStream[i].GetChunk(Items);
|
||||||
|
if cnt > 0 then
|
||||||
|
begin
|
||||||
|
var d := Items[cnt - 1];
|
||||||
|
str := FRandomStream[i].Symbol + ' ' + cnt.ToString + ' ' + d.Time.ToString + ': ' + d.Data.Ask.ToString;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
LogMemo.Lines[i] := str;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
LogMemo.EndUpdate;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Projektplan: Meilenstein Daten-Infrastruktur
|
||||||
|
|
||||||
|
*Datum: 13. Juni 2025*
|
||||||
|
|
||||||
|
## Status: Design-Phase abgeschlossen, Basis-Implementierung erfolgt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stufe 1: Asynchrone Datenstrom-Schnittstelle (`IDataStream<T>`) - **ABGESCHLOSSEN**
|
||||||
|
|
||||||
|
* **Motivation**: Das ursprüngliche, zustandsbehaftete Design war für die Verarbeitung asynchron eintreffender Daten (z.B. von `TFuture`-Objekten) zu komplex und fehleranfällig.
|
||||||
|
|
||||||
|
* **Ziel**: Die Schaffung eines robusten, ereignisgesteuerten und non-blocking Modells, das den Datenproduzenten sauber vom Konsumenten entkoppelt.
|
||||||
|
|
||||||
|
* **Ergebnis**:
|
||||||
|
* Die `IDataStream<T>`-Schnittstelle wurde überarbeitet. Die `HasData`-Eigenschaft liefert nun ein `TSignal`, das Konsumenten aktiv über potenziell neue Daten informiert.
|
||||||
|
* Die Referenzimplementierung `TAuraFileStream<T>` wurde erfolgreich angepasst und nutzt eine saubere Ereignis-Kopplung (`Subscribe`) für eine robuste und wartungsarme Logik.
|
||||||
|
* Die korrekte Funktionalität wurde durch eine angepasste DUnitX-Test-Suite verifiziert.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stufe 2: Abstraktion für Handelssysteme (`IDataSeriesProvider`) - **ENTWORFEN**
|
||||||
|
|
||||||
|
* **Motivation**: Ein Handelssystem benötigt einen stets validen und kontinuierlichen Daten-Lookback. Ein roher `IDataStream<T>` kann dies nicht garantieren, da Lücken in den Daten auftreten können (z.B. beim Übergang von Historie zu Live).
|
||||||
|
|
||||||
|
* **Ziel**: Die Konzeption einer übergeordneten Abstraktionsschicht, die diese komplexe Anforderung kapselt, die Datenintegrität sicherstellt und dem Handelssystem eine einfache, sichere Schnittstelle bietet.
|
||||||
|
|
||||||
|
* **Ergebnis**:
|
||||||
|
Entworfen wurde der `IDataSeriesProvider`, der als "Black Box" für das Handelssystem fungiert und die Komplexität der Datenbeschaffung vollständig verbirgt. Er wurde mit zwei unterschiedlichen, vom Anwender wählbaren Betriebsmodi konzipiert:
|
||||||
|
|
||||||
|
### Modus 1: "Live-Handel"
|
||||||
|
* **Motivation**: Um einen echten **"Sofort-Start"** im Live-Handel zu ermöglichen, muss die Lücke zwischen den statischen, lokalen Historiendaten und dem aktuellen Zeitpunkt geschlossen werden.
|
||||||
|
* **Ziel**: Ein lückenloser, tagesaktueller Start des Handelssystems ohne manuelles Eingreifen oder lange Wartezeiten für den Nutzer.
|
||||||
|
* **Ergebnis**: Das Design einer **Drei-Phasen-Synchronisation**: (1) Lokale History laden, (2) "Catch-up"-Daten vom Broker holen, (3) auf den Live-Stream umschalten.
|
||||||
|
|
||||||
|
### Modus 2: "Simulation & Backtest"
|
||||||
|
* **Motivation**: Für Entwicklung, Test und Analyse muss die Software **völlig autonom** und ohne Abhängigkeit von einer externen, potenziell nicht verfügbaren Broker-API lauffähig sein.
|
||||||
|
* **Ziel**: Einen "Sofort-Start" für Backtests zu jedem beliebigen Zeitpunkt in der Vergangenheit zu ermöglichen, der rein auf lokalen Dateien basiert.
|
||||||
|
* **Ergebnis**: Ein Design, bei dem der relevante Datenkontext in den Speicher geladen wird, um von dort aus einen schnellen Start und ein "Playback" der Daten zu ermöglichen.
|
||||||
|
|
||||||
|
# TODO-Einträge: Dateninfrastruktur
|
||||||
|
|
||||||
|
## Stufe 2: `IDataSeriesProvider`
|
||||||
|
|
||||||
|
- [ ] **Interface und Basis-Implementierung erstellen**
|
||||||
|
* Definiere das `IDataSeriesProvider<T>`-Interface.
|
||||||
|
* Definiere die `TProviderStatus`-Enumeration (`psInitializing`, `psReadyContinuous`, `psReadyWithGap`, `psFaulted`).
|
||||||
|
* Implementiere eine abstrakte Basisklasse `TDataSeriesProvider<T>`, die die grundlegenden Felder (z.B. für die interne `TDataSeries`) und Methoden bereitstellt.
|
||||||
|
|
||||||
|
- [ ] **Simulations-Modus implementieren**
|
||||||
|
* Erstelle eine konkrete Implementierung (z.B. `TSimulationSeriesProvider<T>`).
|
||||||
|
* Implementiere die Start-Logik:
|
||||||
|
* Laden des gesamten relevanten Datenkontexts via `TAuraDataServer.LoadDataSeries`.
|
||||||
|
* Positionierung auf einen wählbaren Startzeitpunkt via `TDataSeries.IndexOf`.
|
||||||
|
* Extrahieren des initialen Lookback-Puffers.
|
||||||
|
* Implementiere das "Playback" der nachfolgenden Datenpunkte aus der im Speicher gehaltenen Serie.
|
||||||
|
* Setze den `Status` korrekt auf `psReadyContinuous`, da im Simulationsmodus per Definition keine Lücke zur "Gegenwart" existiert.
|
||||||
|
|
||||||
|
- [ ] **Live-Handel Modus implementieren**
|
||||||
|
* **Voraussetzung**: Definiere eine Schnittstelle für Live-Datenquellen (z.B. `ILiveDataSource<T>`), die eine Methode zur Abfrage rezenter historischer Daten (`GetCatchUpData`) enthält.
|
||||||
|
* **Start-Logik (Drei-Phasen-Synchronisation)**:
|
||||||
|
* Implementiere den parallelen Start der asynchronen Operationen:
|
||||||
|
1. Laden der lokalen Historie.
|
||||||
|
2. Herstellen der Broker-Verbindung und Abrufen der "Catch-up"-Daten.
|
||||||
|
* Implementiere die "Splicing"-Logik, die beide Datenquellen zu einem nahtlosen Lookback-Puffer verbindet.
|
||||||
|
* Implementiere die Prüfung auf ein "Markt-Gap" beim Übergang, um den initialen Status korrekt auf `psReadyContinuous` oder `psReadyWithGap` zu setzen.
|
||||||
|
* **Laufzeit-Logik**:
|
||||||
|
* Implementiere das kontinuierliche Verarbeiten des Live-Streams.
|
||||||
|
* Implementiere die Fehlerbehandlung für Laufzeit-Unterbrechungen (Verbindungsverlust), inklusive des Wechsels in den `psFaulted`-Status und des sicheren "Re-Priming".
|
||||||
|
|
||||||
|
- [ ] **Session-Kalender entwerfen und implementieren**
|
||||||
|
* **Motivation**: Notwendig für die Unterscheidung zwischen "Markt-Gaps" (natürlich) und "Daten-Lücken" (Fehler) im Live-Betrieb.
|
||||||
|
* **Anforderung**: Muss pro Asset konfigurierbar sein (Handelszeiten, Feiertage).
|
||||||
|
* **Implementierung**: Erstelle eine Klasse, die für ein gegebenes Zeitintervall prüfen kann, ob der Markt geöffnet oder geschlossen war. Integriere diese Prüfung in die Laufzeit-Logik des `TDataSeriesProvider` im Live-Modus.
|
||||||
@@ -49,7 +49,9 @@ var
|
|||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
{$IFDEF MSWINDOWS} Winapi.Windows, {$endif}
|
{$IFDEF MSWINDOWS}
|
||||||
|
Winapi.Windows,
|
||||||
|
{$endif}
|
||||||
System.Math;
|
System.Math;
|
||||||
|
|
||||||
{ TDataFileCache<T> }
|
{ TDataFileCache<T> }
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ type
|
|||||||
protected
|
protected
|
||||||
function GetValue: T; virtual; abstract;
|
function GetValue: T; virtual; abstract;
|
||||||
function GetDone: TState; virtual; abstract;
|
function GetDone: TState; virtual; abstract;
|
||||||
|
public
|
||||||
|
procedure BeforeDestruction; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TMycNullFuture<T> = class(TMycFuture<T>)
|
TMycNullFuture<T> = class(TMycFuture<T>)
|
||||||
@@ -34,8 +36,27 @@ type
|
|||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TMycFutureManaged<T> = class(TMycFuture<T>)
|
||||||
|
private
|
||||||
|
FFuture: TFuture<T>.IFuture;
|
||||||
|
protected
|
||||||
|
function GetValue: T; override;
|
||||||
|
function GetDone: TState; override;
|
||||||
|
public
|
||||||
|
constructor Create(const AFuture: TFuture<T>.IFuture);
|
||||||
|
destructor Destroy; override;
|
||||||
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
|
{ TMycFuture<T> }
|
||||||
|
|
||||||
|
procedure TMycFuture<T>.BeforeDestruction;
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
|
Assert(GetDone.IsSet, 'Trying to destroy an unfinished future');
|
||||||
|
end;
|
||||||
|
|
||||||
{ TMycNullFuture<T> }
|
{ TMycNullFuture<T> }
|
||||||
|
|
||||||
function TMycNullFuture<T>.GetDone: TState;
|
function TMycNullFuture<T>.GetDone: TState;
|
||||||
@@ -79,8 +100,6 @@ end;
|
|||||||
|
|
||||||
destructor TMycGateFuncFuture<T>.Destroy;
|
destructor TMycGateFuncFuture<T>.Destroy;
|
||||||
begin
|
begin
|
||||||
Assert(FDone.State.IsSet, 'Future not done');
|
|
||||||
|
|
||||||
FInit.Unsubscribe;
|
FInit.Unsubscribe;
|
||||||
inherited Destroy;
|
inherited Destroy;
|
||||||
end;
|
end;
|
||||||
@@ -96,4 +115,34 @@ begin
|
|||||||
Result := FResult;
|
Result := FResult;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ TMycFutureManaged<T> }
|
||||||
|
|
||||||
|
constructor TMycFutureManaged<T>.Create(const AFuture: TFuture<T>.IFuture);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FFuture := AFuture;
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TMycFutureManaged<T>.Destroy;
|
||||||
|
begin
|
||||||
|
if GetTypeKind(T) = tkClass then
|
||||||
|
begin
|
||||||
|
var val := GetValue;
|
||||||
|
var obj := TObject(PPointer(@val)^);
|
||||||
|
if obj <> nil then
|
||||||
|
obj.Free;
|
||||||
|
end;
|
||||||
|
inherited;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycFutureManaged<T>.GetDone: TState;
|
||||||
|
begin
|
||||||
|
Result := FFuture.Done;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TMycFutureManaged<T>.GetValue: T;
|
||||||
|
begin
|
||||||
|
Result := FFuture.Value;
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ type
|
|||||||
|
|
||||||
class property Null: IFuture read FNull;
|
class property Null: IFuture read FNull;
|
||||||
|
|
||||||
|
procedure Manage;
|
||||||
|
|
||||||
function Chain<S>(const Proc: TFunc<T, S>): TFuture<S>; overload;
|
function Chain<S>(const Proc: TFunc<T, S>): TFuture<S>; overload;
|
||||||
function Chain<S>(const Proc: TFuncConst<T, S>): TFuture<S>; overload;
|
function Chain<S>(const Proc: TFuncConst<T, S>): TFuture<S>; overload;
|
||||||
function WaitFor: T;
|
function WaitFor: T;
|
||||||
@@ -113,6 +115,12 @@ begin
|
|||||||
Result := FFuture.Value;
|
Result := FFuture.Value;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TFuture<T>.Manage;
|
||||||
|
begin
|
||||||
|
if GetTypeKind(T) = tkClass then
|
||||||
|
FFuture := TMycFutureManaged<T>.Create(FFuture);
|
||||||
|
end;
|
||||||
|
|
||||||
function TFuture<T>.WaitFor: T;
|
function TFuture<T>.WaitFor: T;
|
||||||
begin
|
begin
|
||||||
TaskManager.WaitFor(FFuture.Done);
|
TaskManager.WaitFor(FFuture.Done);
|
||||||
|
|||||||
+32
-19
@@ -4,23 +4,27 @@ interface
|
|||||||
|
|
||||||
uses
|
uses
|
||||||
System.Classes,
|
System.Classes,
|
||||||
|
System.SysUtils,
|
||||||
FMX.Controls,
|
FMX.Controls,
|
||||||
Myc.Signals;
|
Myc.Signals;
|
||||||
|
|
||||||
type
|
type
|
||||||
TFMXSignalLink = class(TComponent, TSignal.ISubscriber)
|
TFMXValidationState = class(TComponent, TSignal.ISubscriber)
|
||||||
private
|
private
|
||||||
[volatile]
|
[volatile]
|
||||||
FInvalidated: Integer;
|
FInvalidated: Integer;
|
||||||
procedure InvalidateControl;
|
FSignal: TSignal.ISignal;
|
||||||
|
FSubscription: TSignal.TSubscriptionTag;
|
||||||
|
FProc: TProc;
|
||||||
|
procedure CallProc;
|
||||||
protected
|
protected
|
||||||
function Notify: Boolean;
|
function Notify: Boolean;
|
||||||
|
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TControl); reintroduce;
|
constructor Create(AOwner: TControl; const ASignal: TSignal.ISignal; const AProc: TProc); reintroduce;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
function Reset: Boolean;
|
procedure AfterConstruction; override;
|
||||||
|
procedure BeforeDestruction; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
@@ -28,36 +32,45 @@ implementation
|
|||||||
uses
|
uses
|
||||||
System.SyncObjs;
|
System.SyncObjs;
|
||||||
|
|
||||||
{ TFMXSignalLink }
|
{ TFMXValidationState }
|
||||||
|
|
||||||
constructor TFMXSignalLink.Create(AOwner: TControl);
|
constructor TFMXValidationState.Create(AOwner: TControl; const ASignal: TSignal.ISignal; const AProc: TProc);
|
||||||
begin
|
begin
|
||||||
inherited Create(AOwner);
|
inherited Create(AOwner);
|
||||||
FInvalidated := 0;
|
FInvalidated := 0;
|
||||||
|
FSignal := ASignal;
|
||||||
|
FProc := AProc;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TFMXSignalLink.Destroy;
|
destructor TFMXValidationState.Destroy;
|
||||||
begin
|
begin
|
||||||
TThread.RemoveQueuedEvents(InvalidateControl);
|
TThread.RemoveQueuedEvents(CallProc);
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TFMXSignalLink.InvalidateControl;
|
procedure TFMXValidationState.AfterConstruction;
|
||||||
begin
|
begin
|
||||||
with TControl(Owner) do
|
inherited;
|
||||||
InvalidateRect(LocalRect);
|
FSubscription := FSignal.Subscribe(Self);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TFMXSignalLink.Notify: Boolean;
|
procedure TFMXValidationState.BeforeDestruction;
|
||||||
|
begin
|
||||||
|
FSignal.Unsubscribe(FSubscription);
|
||||||
|
inherited;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TFMXValidationState.CallProc;
|
||||||
|
begin
|
||||||
|
if TInterlocked.Exchange(FInvalidated, 0) > 0 then
|
||||||
|
FProc;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TFMXValidationState.Notify: Boolean;
|
||||||
begin
|
begin
|
||||||
if TInterlocked.Exchange(FInvalidated, 1) = 0 then
|
if TInterlocked.Exchange(FInvalidated, 1) = 0 then
|
||||||
TThread.Queue(nil, InvalidateControl);
|
TThread.Queue(nil, CallProc);
|
||||||
exit(true);
|
exit(true);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TFMXSignalLink.Reset: Boolean;
|
|
||||||
begin
|
|
||||||
Result := TInterlocked.Exchange(FInvalidated, 0) > 0;
|
|
||||||
end;
|
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
+33
-12
@@ -3,8 +3,7 @@ unit Myc.Signals;
|
|||||||
interface
|
interface
|
||||||
|
|
||||||
uses
|
uses
|
||||||
System.SysUtils,
|
System.SysUtils;
|
||||||
System.SyncObjs;
|
|
||||||
|
|
||||||
type
|
type
|
||||||
TSignal = record
|
TSignal = record
|
||||||
@@ -137,8 +136,7 @@ type
|
|||||||
strict private
|
strict private
|
||||||
class var
|
class var
|
||||||
FNull: ILatch;
|
FNull: ILatch;
|
||||||
FQueueGate: TLatch;
|
FQueueLock: Integer;
|
||||||
FQueueLock: TSpinLock;
|
|
||||||
class constructor ClassCreate;
|
class constructor ClassCreate;
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -153,8 +151,8 @@ type
|
|||||||
|
|
||||||
class function CreateLatch(Count: Integer): TLatch; static;
|
class function CreateLatch(Count: Integer): TLatch; static;
|
||||||
|
|
||||||
class function Enqueue(var Gate: TLatch): TState; overload; static;
|
class function Enqueue1(var Gate: TLatch): TState; overload; static;
|
||||||
class function Enqueue: TState; overload; static;
|
function Enqueue: TState; overload;
|
||||||
|
|
||||||
class property Null: ILatch read FNull;
|
class property Null: ILatch read FNull;
|
||||||
|
|
||||||
@@ -394,7 +392,7 @@ class constructor TLatch.ClassCreate;
|
|||||||
begin
|
begin
|
||||||
// Create a singleton null latch instance that is initially (and always) set.
|
// Create a singleton null latch instance that is initially (and always) set.
|
||||||
FNull := TMycNullLatch.Create;
|
FNull := TMycNullLatch.Create;
|
||||||
FQueueLock := TSpinLock.Create(false);
|
FQueueLock := 0;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
constructor TLatch.Create(const ALatch: TLatch.ILatch);
|
constructor TLatch.Create(const ALatch: TLatch.ILatch);
|
||||||
@@ -412,23 +410,46 @@ begin
|
|||||||
Result := FNull;
|
Result := FNull;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TLatch.Enqueue(var Gate: TLatch): TState;
|
class function TLatch.Enqueue1(var Gate: TLatch): TState;
|
||||||
begin
|
begin
|
||||||
var Latch: TLatch.ILatch := TMycLatch.Create(1);
|
var Latch: TLatch.ILatch := TMycLatch.Create(1);
|
||||||
|
|
||||||
FQueueLock.Enter;
|
repeat
|
||||||
|
if FQueueLock > 0 then
|
||||||
|
begin
|
||||||
|
YieldProcessor;
|
||||||
|
continue;
|
||||||
|
end;
|
||||||
|
until AtomicCmpExchange(FQueueLock, 1, 0) = 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
Gate.State.Subscribe(Latch);
|
Gate.State.Subscribe(Latch);
|
||||||
Gate := Latch;
|
Gate := Latch;
|
||||||
Result := Latch.State;
|
Result := Latch.State;
|
||||||
finally
|
finally
|
||||||
FQueueLock.Exit;
|
AtomicDecrement(FQueueLock);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TLatch.Enqueue: TState;
|
function TLatch.Enqueue: TState;
|
||||||
begin
|
begin
|
||||||
Result := Enqueue(FQueueGate);
|
var Latch: ILatch := TMycLatch.Create(1);
|
||||||
|
|
||||||
|
repeat
|
||||||
|
if FQueueLock > 0 then
|
||||||
|
begin
|
||||||
|
YieldProcessor;
|
||||||
|
continue;
|
||||||
|
end;
|
||||||
|
until AtomicCmpExchange(FQueueLock, 1, 0) = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
FLatch.State.Subscribe(Latch);
|
||||||
|
FLatch := Latch;
|
||||||
|
Result := FLatch.State;
|
||||||
|
finally
|
||||||
|
AtomicDecrement(FQueueLock);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TLatch.GetState: TState;
|
function TLatch.GetState: TState;
|
||||||
|
|||||||
+264
-269
@@ -30,34 +30,6 @@ uses
|
|||||||
Myc.Core.FileCache;
|
Myc.Core.FileCache;
|
||||||
|
|
||||||
type
|
type
|
||||||
// Represents metadata for a single data file.
|
|
||||||
TDataFile = record
|
|
||||||
private
|
|
||||||
FExtension: String;
|
|
||||||
FPath: String;
|
|
||||||
FSymbol: String;
|
|
||||||
FYear: Integer;
|
|
||||||
FMonth: Integer;
|
|
||||||
function GetIsValid: Boolean;
|
|
||||||
public
|
|
||||||
constructor Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer);
|
|
||||||
function GetBaseFileName: string;
|
|
||||||
function GetFullFileName: string;
|
|
||||||
// Gets the next consecutive data file, if it exists on disk.
|
|
||||||
function GetNextFile: TDataFile;
|
|
||||||
class function ParseFileName(const FileName: string): TDataFile; static;
|
|
||||||
// Scans a directory to find the oldest file for a specific symbol.
|
|
||||||
class function FindFirst(const DirectoryPath, Symbol: string): TDataFile; static;
|
|
||||||
// Scans a directory and returns the oldest file found for each symbol.
|
|
||||||
class function FindOldestPerSymbol(const DirectoryPath: string): TArray<TDataFile>; static;
|
|
||||||
property IsValid: Boolean read GetIsValid;
|
|
||||||
property Extension: String read FExtension;
|
|
||||||
property Path: String read FPath;
|
|
||||||
property Symbol: String read FSymbol;
|
|
||||||
property Year: Integer read FYear;
|
|
||||||
property Month: Integer read FMonth;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Represents a generic data stream capable of providing sequential data chunks.
|
// Represents a generic data stream capable of providing sequential data chunks.
|
||||||
// IsHistory:
|
// IsHistory:
|
||||||
// - true, if this stream is a history stream. Once HasData becomes false, it reached it's end and will not provide more data.
|
// - true, if this stream is a history stream. Once HasData becomes false, it reached it's end and will not provide more data.
|
||||||
@@ -99,34 +71,66 @@ type
|
|||||||
['{1F8E5A9D-E92A-44C1-9F3F-C4B82A6E94B3}']
|
['{1F8E5A9D-E92A-44C1-9F3F-C4B82A6E94B3}']
|
||||||
function CreateStream(const Symbol: String): IDataStream<T>;
|
function CreateStream(const Symbol: String): IDataStream<T>;
|
||||||
procedure ClearCache;
|
procedure ClearCache;
|
||||||
function EnumerateSymbols: TArray<String>;
|
function EnumerateSymbols: TFuture<TArray<String>>;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Aura files
|
// Aura files
|
||||||
|
|
||||||
|
// Represents metadata for a single data file.
|
||||||
|
TAuraDataFile = record
|
||||||
|
private
|
||||||
|
FExtension: String;
|
||||||
|
FPath: String;
|
||||||
|
FSymbol: String;
|
||||||
|
FYear: Integer;
|
||||||
|
FMonth: Integer;
|
||||||
|
function GetIsValid: Boolean;
|
||||||
|
public
|
||||||
|
constructor Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer);
|
||||||
|
function GetBaseFileName: string;
|
||||||
|
function GetFullFileName: string;
|
||||||
|
// Gets the next consecutive data file, if it exists on disk.
|
||||||
|
function GetNextFile: TAuraDataFile;
|
||||||
|
property IsValid: Boolean read GetIsValid;
|
||||||
|
property Extension: String read FExtension;
|
||||||
|
property Path: String read FPath;
|
||||||
|
property Symbol: String read FSymbol;
|
||||||
|
property Year: Integer read FYear;
|
||||||
|
property Month: Integer read FMonth;
|
||||||
|
end;
|
||||||
|
|
||||||
// Generic server for loading and managing sequential time-series data from files.
|
// Generic server for loading and managing sequential time-series data from files.
|
||||||
TAuraDataServer<T: record> = class(TInterfacedObject, IDataServer<T>)
|
TAuraDataServer<T: record> = class(TInterfacedObject, IDataServer<T>)
|
||||||
private
|
private
|
||||||
FCachedFiles: TDataFileCache<TArray<TDataPoint<T>>>;
|
FCachedFiles: TDataFileCache<TArray<TDataPoint<T>>>;
|
||||||
FPath: String;
|
FPath: String;
|
||||||
function DoLoad(const FileName: string): TFuture<TArray<TDataPoint<T>>>;
|
FSymbols: TFuture<TDictionary<String, TAuraDataFile>>;
|
||||||
function GetPath: String;
|
function GetPath: String;
|
||||||
|
|
||||||
|
// Used by cache to actually load an uncached file.
|
||||||
|
function DoLoad(const FileName: string): TFuture<TArray<TDataPoint<T>>>;
|
||||||
|
|
||||||
strict private
|
strict private
|
||||||
class var
|
class var
|
||||||
FLoadGate: TLatch;
|
FLoadGate: TLatch;
|
||||||
|
|
||||||
protected
|
protected
|
||||||
class function ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<T>>; static;
|
// Scans a directory to find the oldest file for a specific symbol.
|
||||||
class function ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<T>>; static;
|
function FindFirstFile(const Symbol: string): TFuture<TAuraDataFile>;
|
||||||
|
function ParseFileName(const FileName: string): TAuraDataFile; virtual; abstract;
|
||||||
|
|
||||||
|
class function ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<T>>; virtual; abstract;
|
||||||
|
class function ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<T>>; virtual; abstract;
|
||||||
|
|
||||||
public
|
public
|
||||||
constructor Create(const APath: String);
|
constructor Create(const APath: String);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
procedure AfterConstruction; override;
|
||||||
function CreateStream(const Symbol: String): IDataStream<T>; virtual; abstract;
|
function CreateStream(const Symbol: String): IDataStream<T>; virtual; abstract;
|
||||||
procedure ClearCache;
|
procedure ClearCache;
|
||||||
function EnumerateSymbols: TArray<String>;
|
procedure UpdateSymbols;
|
||||||
function LoadDataFile(const DataFile: TDataFile): TFuture<TArray<TDataPoint<T>>>;
|
function EnumerateSymbols: TFuture<TArray<String>>;
|
||||||
|
function LoadDataFile(const DataFile: TAuraDataFile): TFuture<TArray<TDataPoint<T>>>;
|
||||||
property Path: String read GetPath;
|
property Path: String read GetPath;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -135,7 +139,7 @@ type
|
|||||||
private
|
private
|
||||||
type
|
type
|
||||||
TDataStream = record
|
TDataStream = record
|
||||||
FileInfo: TDataFile;
|
FileInfo: TAuraDataFile;
|
||||||
Data: TFuture<TArray<TDataPoint<T>>>;
|
Data: TFuture<TArray<TDataPoint<T>>>;
|
||||||
end;
|
end;
|
||||||
private
|
private
|
||||||
@@ -162,9 +166,14 @@ type
|
|||||||
// Aura tick data file Ask-Bid
|
// Aura tick data file Ask-Bid
|
||||||
|
|
||||||
TAuraTABFileServer = class(TAuraDataServer<TAskBidItem>)
|
TAuraTABFileServer = class(TAuraDataServer<TAskBidItem>)
|
||||||
|
protected
|
||||||
|
function ParseFileName(const FileName: string): TAuraDataFile; override;
|
||||||
|
// Scans a directory and returns the oldest file found for each symbol.
|
||||||
|
class function ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<TAskBidItem>>; override;
|
||||||
|
class function ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<TAskBidItem>>; override;
|
||||||
public
|
public
|
||||||
function CreateStream(const Symbol: String): IDataStream<TAskBidItem>; override;
|
function CreateStream(const Symbol: String): IDataStream<TAskBidItem>; override;
|
||||||
function LoadDataSeries(const InitialFile: TDataFile): TFuture<TArray<TDataPoint<TAskBidItem>>>;
|
function LoadDataSeries(const InitialFile: TAuraDataFile): TFuture<TArray<TDataPoint<TAskBidItem>>>;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
@@ -175,9 +184,9 @@ uses
|
|||||||
System.StrUtils,
|
System.StrUtils,
|
||||||
Myc.TaskManager;
|
Myc.TaskManager;
|
||||||
|
|
||||||
{ TDataFile }
|
{ TAuraDataFile }
|
||||||
|
|
||||||
constructor TDataFile.Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer);
|
constructor TAuraDataFile.Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer);
|
||||||
begin
|
begin
|
||||||
FPath := APath;
|
FPath := APath;
|
||||||
FSymbol := ASymbol;
|
FSymbol := ASymbol;
|
||||||
@@ -186,28 +195,28 @@ begin
|
|||||||
FMonth := AMonth;
|
FMonth := AMonth;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataFile.GetBaseFileName: string;
|
function TAuraDataFile.GetBaseFileName: string;
|
||||||
begin
|
begin
|
||||||
Result := Format('%s_%.4d_%.2d', [FSymbol, FYear, FMonth]);
|
Result := Format('%s_%.4d_%.2d', [FSymbol, FYear, FMonth]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataFile.GetFullFileName: string;
|
function TAuraDataFile.GetFullFileName: string;
|
||||||
begin
|
begin
|
||||||
Result := TPath.Combine(FPath, GetBaseFileName + FExtension);
|
Result := TPath.Combine(FPath, GetBaseFileName + FExtension);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataFile.GetIsValid: Boolean;
|
function TAuraDataFile.GetIsValid: Boolean;
|
||||||
begin
|
begin
|
||||||
Result := (FSymbol <> '') and (FYear > 0);
|
Result := (FSymbol <> '') and (FYear > 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataFile.GetNextFile: TDataFile;
|
function TAuraDataFile.GetNextFile: TAuraDataFile;
|
||||||
var
|
var
|
||||||
nextMonth, nextYear: Integer;
|
nextMonth, nextYear: Integer;
|
||||||
nextFile: TDataFile;
|
nextFile: TAuraDataFile;
|
||||||
begin
|
begin
|
||||||
if not IsValid then
|
if not IsValid then
|
||||||
exit(Default(TDataFile));
|
exit(Default(TAuraDataFile));
|
||||||
|
|
||||||
nextMonth := FMonth + 1;
|
nextMonth := FMonth + 1;
|
||||||
nextYear := FYear;
|
nextYear := FYear;
|
||||||
@@ -218,137 +227,19 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// Probe for zipped file first
|
// Probe for zipped file first
|
||||||
nextFile := TDataFile.Create(FPath, FSymbol, '.tab_zip', nextYear, nextMonth);
|
nextFile := TAuraDataFile.Create(FPath, FSymbol, '.tab_zip', nextYear, nextMonth);
|
||||||
if TFile.Exists(nextFile.GetFullFileName) then
|
if TFile.Exists(nextFile.GetFullFileName) then
|
||||||
exit(nextFile);
|
exit(nextFile);
|
||||||
|
|
||||||
// Probe for uncompressed file
|
// Probe for uncompressed file
|
||||||
nextFile := TDataFile.Create(FPath, FSymbol, '.tab', nextYear, nextMonth);
|
nextFile := TAuraDataFile.Create(FPath, FSymbol, '.tab', nextYear, nextMonth);
|
||||||
if TFile.Exists(nextFile.GetFullFileName) then
|
if TFile.Exists(nextFile.GetFullFileName) then
|
||||||
exit(nextFile);
|
exit(nextFile);
|
||||||
|
|
||||||
// No next file found
|
// No next file found
|
||||||
Result := Default(TDataFile);
|
Result := Default(TAuraDataFile);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TDataFile.ParseFileName(const FileName: string): TDataFile;
|
|
||||||
var
|
|
||||||
fileNameNoPath, nameForParsing, baseName, ext, path, symbol: string;
|
|
||||||
year, month: Integer;
|
|
||||||
parts: TArray<string>;
|
|
||||||
begin
|
|
||||||
Result := Default(TDataFile);
|
|
||||||
|
|
||||||
path := TPath.GetDirectoryName(FileName);
|
|
||||||
fileNameNoPath := TPath.GetFileName(FileName);
|
|
||||||
nameForParsing := fileNameNoPath;
|
|
||||||
|
|
||||||
ext := '';
|
|
||||||
if nameForParsing.EndsWith('_zip', True) then
|
|
||||||
begin
|
|
||||||
ext := '_zip';
|
|
||||||
nameForParsing := nameForParsing.Substring(0, nameForParsing.Length - 4);
|
|
||||||
end;
|
|
||||||
|
|
||||||
var fileExt := TPath.GetExtension(nameForParsing) + ext;
|
|
||||||
baseName := TPath.GetFileNameWithoutExtension(nameForParsing);
|
|
||||||
|
|
||||||
parts := baseName.Split(['_']);
|
|
||||||
|
|
||||||
if Length(parts) < 3 then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
if not TryStrToInt(parts[High(parts)], month) then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
if (month < 1) or (month > 12) then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
if not TryStrToInt(parts[High(parts) - 1], year) then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
if year <= 0 then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
symbol := string.Join('_', Copy(parts, 0, Length(parts) - 2));
|
|
||||||
|
|
||||||
if symbol = '' then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
Result := TDataFile.Create(path, symbol, fileExt, year, month);
|
|
||||||
end;
|
|
||||||
|
|
||||||
class function TDataFile.FindFirst(const DirectoryPath, Symbol: string): TDataFile;
|
|
||||||
var
|
|
||||||
fileNames: TArray<string>;
|
|
||||||
currentFileName: string;
|
|
||||||
parsedFile: TDataFile;
|
|
||||||
begin
|
|
||||||
Result := Default(TDataFile);
|
|
||||||
|
|
||||||
if not TDirectory.Exists(DirectoryPath) then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
fileNames := TDirectory.GetFiles(DirectoryPath);
|
|
||||||
for currentFileName in fileNames do
|
|
||||||
begin
|
|
||||||
parsedFile := TDataFile.ParseFileName(currentFileName);
|
|
||||||
if parsedFile.IsValid then
|
|
||||||
begin
|
|
||||||
if SameText(parsedFile.Symbol, Symbol) then
|
|
||||||
begin
|
|
||||||
// If it's the first match or older than the current result, update.
|
|
||||||
if (not Result.IsValid) or
|
|
||||||
(parsedFile.Year < Result.Year) or
|
|
||||||
((parsedFile.Year = Result.Year) and (parsedFile.Month < Result.Month)) then
|
|
||||||
begin
|
|
||||||
Result := parsedFile;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
class function TDataFile.FindOldestPerSymbol(const DirectoryPath: string): TArray<TDataFile>;
|
|
||||||
var
|
|
||||||
oldestFilesPerSymbol: TDictionary<string, TDataFile>;
|
|
||||||
fileNames: TArray<string>;
|
|
||||||
currentFile: string;
|
|
||||||
dataFile: TDataFile;
|
|
||||||
trackedInfo: TDataFile;
|
|
||||||
begin
|
|
||||||
oldestFilesPerSymbol := TDictionary<string, TDataFile>.Create;
|
|
||||||
try
|
|
||||||
if not TDirectory.Exists(DirectoryPath) then
|
|
||||||
exit(nil);
|
|
||||||
|
|
||||||
fileNames := TDirectory.GetFiles(DirectoryPath);
|
|
||||||
for currentFile in fileNames do
|
|
||||||
begin
|
|
||||||
dataFile := TDataFile.ParseFileName(currentFile);
|
|
||||||
if dataFile.IsValid then
|
|
||||||
begin
|
|
||||||
if oldestFilesPerSymbol.TryGetValue(dataFile.Symbol, trackedInfo) then
|
|
||||||
begin
|
|
||||||
if (dataFile.Year < trackedInfo.Year) or ((dataFile.Year = trackedInfo.Year) and (dataFile.Month < trackedInfo.Month)) then
|
|
||||||
begin
|
|
||||||
oldestFilesPerSymbol.AddOrSetValue(dataFile.Symbol, dataFile);
|
|
||||||
end;
|
|
||||||
end
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
oldestFilesPerSymbol.Add(dataFile.Symbol, dataFile);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
Result := oldestFilesPerSymbol.Values.ToArray;
|
|
||||||
finally
|
|
||||||
oldestFilesPerSymbol.Free;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
{ TAuraDataServer<T> }
|
{ TAuraDataServer<T> }
|
||||||
|
|
||||||
constructor TAuraDataServer<T>.Create(const APath: String);
|
constructor TAuraDataServer<T>.Create(const APath: String);
|
||||||
@@ -368,27 +259,17 @@ begin
|
|||||||
inherited Destroy;
|
inherited Destroy;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TAuraDataServer<T>.AfterConstruction;
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
|
UpdateSymbols;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TAuraDataServer<T>.ClearCache;
|
procedure TAuraDataServer<T>.ClearCache;
|
||||||
begin
|
begin
|
||||||
FCachedFiles.Clear;
|
FCachedFiles.Clear;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAuraDataServer<T>.EnumerateSymbols: TArray<String>;
|
|
||||||
var
|
|
||||||
dataFiles: TArray<TDataFile>;
|
|
||||||
i: Integer;
|
|
||||||
begin
|
|
||||||
dataFiles := TDataFile.FindOldestPerSymbol(FPath);
|
|
||||||
SetLength(Result, Length(dataFiles));
|
|
||||||
for i := 0 to High(dataFiles) do
|
|
||||||
Result[i] := dataFiles[i].Symbol;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TAuraDataServer<T>.GetPath: String;
|
|
||||||
begin
|
|
||||||
Result := FPath;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TAuraDataServer<T>.DoLoad(const FileName: string): TFuture<TArray<TDataPoint<T>>>;
|
function TAuraDataServer<T>.DoLoad(const FileName: string): TFuture<TArray<TDataPoint<T>>>;
|
||||||
begin
|
begin
|
||||||
Result := TFuture<TArray<TDataPoint<T>>>.Null;
|
Result := TFuture<TArray<TDataPoint<T>>>.Null;
|
||||||
@@ -401,9 +282,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
Result :=
|
Result :=
|
||||||
TFuture<TBytes>
|
TFuture<TBytes>
|
||||||
.Construct(
|
.Construct(FLoadGate.Enqueue, function: TBytes begin Result := TFile.ReadAllBytes(capFileName); end)
|
||||||
TLatch.Enqueue(FLoadGate),
|
|
||||||
function: TBytes begin Result := TFile.ReadAllBytes(capFileName); end)
|
|
||||||
.Chain<TArray<TDataPoint<T>>>(
|
.Chain<TArray<TDataPoint<T>>>(
|
||||||
function(bytes: TBytes): TArray<TDataPoint<T>>
|
function(bytes: TBytes): TArray<TDataPoint<T>>
|
||||||
begin
|
begin
|
||||||
@@ -420,7 +299,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
Result :=
|
Result :=
|
||||||
TFuture<TArray<TDataPoint<T>>>.Construct(
|
TFuture<TArray<TDataPoint<T>>>.Construct(
|
||||||
TLatch.Enqueue(FLoadGate),
|
FLoadGate.Enqueue,
|
||||||
function: TArray<TDataPoint<T>>
|
function: TArray<TDataPoint<T>>
|
||||||
begin
|
begin
|
||||||
if TFile.Exists(capFileName) then
|
if TFile.Exists(capFileName) then
|
||||||
@@ -442,81 +321,82 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAuraDataServer<T>.LoadDataFile(const DataFile: TDataFile): TFuture<TArray<TDataPoint<T>>>;
|
procedure TAuraDataServer<T>.UpdateSymbols;
|
||||||
|
begin
|
||||||
|
FSymbols :=
|
||||||
|
TFuture<TDictionary<String, TAuraDataFile>>.Construct(
|
||||||
|
FSymbols.Done,
|
||||||
|
function: TDictionary<String, TAuraDataFile>
|
||||||
|
var
|
||||||
|
fileNames: TArray<string>;
|
||||||
|
currentFile: string;
|
||||||
|
dataFile: TAuraDataFile;
|
||||||
|
trackedInfo: TAuraDataFile;
|
||||||
|
begin
|
||||||
|
Result := TDictionary<string, TAuraDataFile>.Create;
|
||||||
|
if not TDirectory.Exists(FPath) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
fileNames := TDirectory.GetFiles(FPath);
|
||||||
|
for currentFile in fileNames do
|
||||||
|
begin
|
||||||
|
dataFile := ParseFileName(currentFile);
|
||||||
|
if dataFile.IsValid then
|
||||||
|
begin
|
||||||
|
if Result.TryGetValue(dataFile.Symbol, trackedInfo) then
|
||||||
|
begin
|
||||||
|
if (dataFile.Year < trackedInfo.Year)
|
||||||
|
or ((dataFile.Year = trackedInfo.Year) and (dataFile.Month < trackedInfo.Month)) then
|
||||||
|
begin
|
||||||
|
Result.AddOrSetValue(dataFile.Symbol, dataFile);
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Result.Add(dataFile.Symbol, dataFile);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
);
|
||||||
|
|
||||||
|
FSymbols.Manage;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAuraDataServer<T>.EnumerateSymbols: TFuture<TArray<String>>;
|
||||||
|
var
|
||||||
|
dataFiles: TArray<TAuraDataFile>;
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
Result :=
|
||||||
|
FSymbols.Chain<TArray<String>>(
|
||||||
|
function(Symbols: TDictionary<String, TAuraDataFile>): TArray<String> begin Result := Symbols.Keys.ToArray; end
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAuraDataServer<T>.FindFirstFile(const Symbol: string): TFuture<TAuraDataFile>;
|
||||||
|
begin
|
||||||
|
var symName := Symbol;
|
||||||
|
Result :=
|
||||||
|
FSymbols.Chain<TAuraDataFile>(
|
||||||
|
function(Symbols: TDictionary<String, TAuraDataFile>): TAuraDataFile
|
||||||
|
begin
|
||||||
|
if not Symbols.TryGetValue(symName, Result) then
|
||||||
|
Result := Default(TAuraDataFile);
|
||||||
|
end
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAuraDataServer<T>.GetPath: String;
|
||||||
|
begin
|
||||||
|
Result := FPath;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TAuraDataServer<T>.LoadDataFile(const DataFile: TAuraDataFile): TFuture<TArray<TDataPoint<T>>>;
|
||||||
begin
|
begin
|
||||||
Result := FCachedFiles.GetOrAdd(DataFile.GetFullFileName);
|
Result := FCachedFiles.GetOrAdd(DataFile.GetFullFileName);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class function TAuraDataServer<T>.ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<T>>;
|
|
||||||
var
|
|
||||||
decompressionStream: TStream;
|
|
||||||
localHeader: TZipHeader;
|
|
||||||
entryIndex, i: Integer;
|
|
||||||
zipFileInstance: TZipFile;
|
|
||||||
begin
|
|
||||||
SetLength(Result, 0);
|
|
||||||
decompressionStream := nil;
|
|
||||||
zipFileInstance := nil;
|
|
||||||
try
|
|
||||||
InputStream.Position := 0;
|
|
||||||
zipFileInstance := TZipFile.Create;
|
|
||||||
zipFileInstance.Open(InputStream, TZipMode.zmRead);
|
|
||||||
|
|
||||||
if zipFileInstance.FileCount = 0 then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
entryIndex := -1;
|
|
||||||
for i := 0 to zipFileInstance.FileCount - 1 do
|
|
||||||
if zipFileInstance.FileNames[i].EndsWith('.tab', True) then
|
|
||||||
begin
|
|
||||||
entryIndex := i;
|
|
||||||
break;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if entryIndex = -1 then
|
|
||||||
entryIndex := 0;
|
|
||||||
|
|
||||||
zipFileInstance.Read(entryIndex, decompressionStream, localHeader);
|
|
||||||
if not Assigned(decompressionStream) then
|
|
||||||
exit;
|
|
||||||
Result := ReadUncompressedData(decompressionStream);
|
|
||||||
finally
|
|
||||||
decompressionStream.Free;
|
|
||||||
zipFileInstance.Free;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
class function TAuraDataServer<T>.ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<T>>;
|
|
||||||
type
|
|
||||||
TFileRecord = packed record
|
|
||||||
TimeStamp: TDateTime;
|
|
||||||
Data: T;
|
|
||||||
end;
|
|
||||||
var
|
|
||||||
fileSize: Int64;
|
|
||||||
recordCount, bytesRead: Integer;
|
|
||||||
rec: TFileRecord;
|
|
||||||
begin
|
|
||||||
SetLength(Result, 0);
|
|
||||||
InputStream.Position := 0;
|
|
||||||
fileSize := InputStream.Size;
|
|
||||||
if (fileSize = 0) or ((fileSize mod SizeOf(TFileRecord)) <> 0) then
|
|
||||||
exit;
|
|
||||||
|
|
||||||
recordCount := fileSize div SizeOf(TFileRecord);
|
|
||||||
if recordCount > 0 then
|
|
||||||
begin
|
|
||||||
SetLength(Result, recordCount);
|
|
||||||
for var i := 0 to High(Result) do
|
|
||||||
begin
|
|
||||||
bytesRead := InputStream.Read(rec, SizeOf(TFileRecord));
|
|
||||||
if bytesRead <> SizeOf(TFileRecord) then
|
|
||||||
raise EReadError.CreateFmt('Read error. Expected %d bytes, read %d.', [fileSize, bytesRead]);
|
|
||||||
Result[i].Create(rec.TimeStamp, rec.Data);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TAuraFileStream<T> }
|
{ TAuraFileStream<T> }
|
||||||
|
|
||||||
constructor TAuraFileStream<T>.Create(ADataServer: TAuraDataServer<T>; const ASymbol: String);
|
constructor TAuraFileStream<T>.Create(ADataServer: TAuraDataServer<T>; const ASymbol: String);
|
||||||
@@ -535,18 +415,16 @@ end;
|
|||||||
|
|
||||||
procedure TAuraFileStream<T>.AfterConstruction;
|
procedure TAuraFileStream<T>.AfterConstruction;
|
||||||
var
|
var
|
||||||
firstFile: TFuture<TDataFile>;
|
firstFile: TFuture<TAuraDataFile>;
|
||||||
serverPath: string;
|
|
||||||
begin
|
begin
|
||||||
inherited;
|
inherited;
|
||||||
|
|
||||||
// We need the path from the server instance to find the file.
|
// We need the path from the server instance to find the file.
|
||||||
serverPath := (FDataServer as TAuraDataServer<T>).Path;
|
firstFile := FDataServer.FindFirstFile(FSymbol);
|
||||||
firstFile := TFuture<TDataFile>.Construct(function: TDataFile begin Result := TDataFile.FindFirst(serverPath, FSymbol); end);
|
|
||||||
|
|
||||||
FCurrent :=
|
FCurrent :=
|
||||||
firstFile.Chain<TDataStream>(
|
firstFile.Chain<TDataStream>(
|
||||||
function(const FileInfo: TDataFile): TDataStream
|
function(const FileInfo: TAuraDataFile): TDataStream
|
||||||
begin
|
begin
|
||||||
if FileInfo.IsValid then
|
if FileInfo.IsValid then
|
||||||
begin
|
begin
|
||||||
@@ -638,7 +516,7 @@ begin
|
|||||||
FCurrent.Chain<TDataStream>(
|
FCurrent.Chain<TDataStream>(
|
||||||
function(const Prev: TDataStream): TDataStream
|
function(const Prev: TDataStream): TDataStream
|
||||||
var
|
var
|
||||||
nextFileInfo: TDataFile;
|
nextFileInfo: TAuraDataFile;
|
||||||
begin
|
begin
|
||||||
if Prev.FileInfo.IsValid then
|
if Prev.FileInfo.IsValid then
|
||||||
begin
|
begin
|
||||||
@@ -661,16 +539,16 @@ begin
|
|||||||
Result := TAuraFileStream<TAskBidItem>.Create(Self, Symbol);
|
Result := TAuraFileStream<TAskBidItem>.Create(Self, Symbol);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAuraTABFileServer.LoadDataSeries(const InitialFile: TDataFile): TFuture<TArray<TDataPoint<TAskBidItem>>>;
|
function TAuraTABFileServer.LoadDataSeries(const InitialFile: TAuraDataFile): TFuture<TArray<TDataPoint<TAskBidItem>>>;
|
||||||
var
|
var
|
||||||
loadedState: TState;
|
loadedState: TState;
|
||||||
loadedFiles: TArray<TFuture<TArray<TDataPoint<TAskBidItem>>>>;
|
loadedFiles: TArray<TFuture<TArray<TDataPoint<TAskBidItem>>>>;
|
||||||
liveData: TFuture<TArray<TDataPoint<TAskBidItem>>>;
|
liveData: TFuture<TArray<TDataPoint<TAskBidItem>>>;
|
||||||
tabFiles: TList<TDataFile>;
|
tabFiles: TList<TAuraDataFile>;
|
||||||
liveFile: TDataFile;
|
liveFile: TAuraDataFile;
|
||||||
currentFileInfo: TDataFile;
|
currentFileInfo: TAuraDataFile;
|
||||||
begin
|
begin
|
||||||
tabFiles := TList<TDataFile>.Create;
|
tabFiles := TList<TAuraDataFile>.Create;
|
||||||
try
|
try
|
||||||
currentFileInfo := InitialFile;
|
currentFileInfo := InitialFile;
|
||||||
while currentFileInfo.IsValid do
|
while currentFileInfo.IsValid do
|
||||||
@@ -679,14 +557,14 @@ begin
|
|||||||
currentFileInfo := currentFileInfo.GetNextFile;
|
currentFileInfo := currentFileInfo.GetNextFile;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
liveFile := Default(TDataFile);
|
liveFile := Default(TAuraDataFile);
|
||||||
if tabFiles.Count > 0 then
|
if tabFiles.Count > 0 then
|
||||||
begin
|
begin
|
||||||
var lastTabFile := tabFiles.Last;
|
var lastTabFile := tabFiles.Last;
|
||||||
var liveFileBaseName := Format('%s_%d_%02d.tab-live', [lastTabFile.Symbol, lastTabFile.Year, lastTabFile.Month]);
|
var liveFileBaseName := Format('%s_%d_%02d.tab-live', [lastTabFile.Symbol, lastTabFile.Year, lastTabFile.Month]);
|
||||||
var potentialLivePath := TPath.Combine(lastTabFile.Path, liveFileBaseName);
|
var potentialLivePath := TPath.Combine(lastTabFile.Path, liveFileBaseName);
|
||||||
if TFile.Exists(potentialLivePath) then
|
if TFile.Exists(potentialLivePath) then
|
||||||
liveFile := TDataFile.ParseFileName(potentialLivePath);
|
liveFile := ParseFileName(potentialLivePath);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
var loadedFileList := TList<TFuture<TArray<TDataPoint<TAskBidItem>>>>.Create;
|
var loadedFileList := TList<TFuture<TArray<TDataPoint<TAskBidItem>>>>.Create;
|
||||||
@@ -745,4 +623,121 @@ begin
|
|||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TAuraTABFileServer.ParseFileName(const FileName: string): TAuraDataFile;
|
||||||
|
var
|
||||||
|
fileNameNoPath, nameForParsing, baseName, ext, path, symbol: string;
|
||||||
|
year, month: Integer;
|
||||||
|
parts: TArray<string>;
|
||||||
|
begin
|
||||||
|
Result := Default(TAuraDataFile);
|
||||||
|
|
||||||
|
path := TPath.GetDirectoryName(FileName);
|
||||||
|
fileNameNoPath := TPath.GetFileName(FileName);
|
||||||
|
nameForParsing := fileNameNoPath;
|
||||||
|
|
||||||
|
ext := '';
|
||||||
|
if nameForParsing.EndsWith('_zip', True) then
|
||||||
|
begin
|
||||||
|
ext := '_zip';
|
||||||
|
nameForParsing := nameForParsing.Substring(0, nameForParsing.Length - 4);
|
||||||
|
end;
|
||||||
|
|
||||||
|
var fileExt := TPath.GetExtension(nameForParsing) + ext;
|
||||||
|
baseName := TPath.GetFileNameWithoutExtension(nameForParsing);
|
||||||
|
|
||||||
|
parts := baseName.Split(['_']);
|
||||||
|
|
||||||
|
if Length(parts) < 3 then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
if not TryStrToInt(parts[High(parts)], month) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
if (month < 1) or (month > 12) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
if not TryStrToInt(parts[High(parts) - 1], year) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
if year <= 0 then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
symbol := string.Join('_', Copy(parts, 0, Length(parts) - 2));
|
||||||
|
|
||||||
|
if symbol = '' then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
Result := TAuraDataFile.Create(path, symbol, fileExt, year, month);
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TAuraTABFileServer.ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<TAskBidItem>>;
|
||||||
|
var
|
||||||
|
decompressionStream: TStream;
|
||||||
|
localHeader: TZipHeader;
|
||||||
|
entryIndex, i: Integer;
|
||||||
|
zipFileInstance: TZipFile;
|
||||||
|
begin
|
||||||
|
SetLength(Result, 0);
|
||||||
|
decompressionStream := nil;
|
||||||
|
zipFileInstance := nil;
|
||||||
|
try
|
||||||
|
InputStream.Position := 0;
|
||||||
|
zipFileInstance := TZipFile.Create;
|
||||||
|
zipFileInstance.Open(InputStream, TZipMode.zmRead);
|
||||||
|
|
||||||
|
if zipFileInstance.FileCount = 0 then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
entryIndex := -1;
|
||||||
|
for i := 0 to zipFileInstance.FileCount - 1 do
|
||||||
|
if zipFileInstance.FileNames[i].EndsWith('.tab', True) then
|
||||||
|
begin
|
||||||
|
entryIndex := i;
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if entryIndex = -1 then
|
||||||
|
entryIndex := 0;
|
||||||
|
|
||||||
|
zipFileInstance.Read(entryIndex, decompressionStream, localHeader);
|
||||||
|
if not Assigned(decompressionStream) then
|
||||||
|
exit;
|
||||||
|
Result := ReadUncompressedData(decompressionStream);
|
||||||
|
finally
|
||||||
|
decompressionStream.Free;
|
||||||
|
zipFileInstance.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TAuraTABFileServer.ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<TAskBidItem>>;
|
||||||
|
type
|
||||||
|
TFileRecord = packed record
|
||||||
|
TimeStamp: TDateTime;
|
||||||
|
Data: TAskBidItem;
|
||||||
|
end;
|
||||||
|
var
|
||||||
|
fileSize: Int64;
|
||||||
|
recordCount, bytesRead: Integer;
|
||||||
|
rec: TFileRecord;
|
||||||
|
begin
|
||||||
|
SetLength(Result, 0);
|
||||||
|
InputStream.Position := 0;
|
||||||
|
fileSize := InputStream.Size;
|
||||||
|
if (fileSize = 0) or ((fileSize mod SizeOf(TFileRecord)) <> 0) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
recordCount := fileSize div SizeOf(TFileRecord);
|
||||||
|
if recordCount > 0 then
|
||||||
|
begin
|
||||||
|
SetLength(Result, recordCount);
|
||||||
|
for var i := 0 to High(Result) do
|
||||||
|
begin
|
||||||
|
bytesRead := InputStream.Read(rec, SizeOf(TFileRecord));
|
||||||
|
if bytesRead <> SizeOf(TFileRecord) then
|
||||||
|
raise EReadError.CreateFmt('Read error. Expected %d bytes, read %d.', [fileSize, bytesRead]);
|
||||||
|
Result[i].Create(rec.TimeStamp, rec.Data);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
Reference in New Issue
Block a user