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
|
||||
|
||||
uses
|
||||
{$IFDEF MSWINDOWS} Winapi.Windows, {$endif}
|
||||
{$IFDEF MSWINDOWS}
|
||||
Winapi.Windows,
|
||||
{$endif}
|
||||
System.Math;
|
||||
|
||||
{ TDataFileCache<T> }
|
||||
|
||||
@@ -13,6 +13,8 @@ type
|
||||
protected
|
||||
function GetValue: T; virtual; abstract;
|
||||
function GetDone: TState; virtual; abstract;
|
||||
public
|
||||
procedure BeforeDestruction; override;
|
||||
end;
|
||||
|
||||
TMycNullFuture<T> = class(TMycFuture<T>)
|
||||
@@ -34,8 +36,27 @@ type
|
||||
destructor Destroy; override;
|
||||
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
|
||||
|
||||
{ TMycFuture<T> }
|
||||
|
||||
procedure TMycFuture<T>.BeforeDestruction;
|
||||
begin
|
||||
inherited;
|
||||
Assert(GetDone.IsSet, 'Trying to destroy an unfinished future');
|
||||
end;
|
||||
|
||||
{ TMycNullFuture<T> }
|
||||
|
||||
function TMycNullFuture<T>.GetDone: TState;
|
||||
@@ -79,8 +100,6 @@ end;
|
||||
|
||||
destructor TMycGateFuncFuture<T>.Destroy;
|
||||
begin
|
||||
Assert(FDone.State.IsSet, 'Future not done');
|
||||
|
||||
FInit.Unsubscribe;
|
||||
inherited Destroy;
|
||||
end;
|
||||
@@ -96,4 +115,34 @@ begin
|
||||
Result := FResult;
|
||||
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.
|
||||
|
||||
@@ -49,6 +49,8 @@ type
|
||||
|
||||
class property Null: IFuture read FNull;
|
||||
|
||||
procedure Manage;
|
||||
|
||||
function Chain<S>(const Proc: TFunc<T, S>): TFuture<S>; overload;
|
||||
function Chain<S>(const Proc: TFuncConst<T, S>): TFuture<S>; overload;
|
||||
function WaitFor: T;
|
||||
@@ -113,6 +115,12 @@ begin
|
||||
Result := FFuture.Value;
|
||||
end;
|
||||
|
||||
procedure TFuture<T>.Manage;
|
||||
begin
|
||||
if GetTypeKind(T) = tkClass then
|
||||
FFuture := TMycFutureManaged<T>.Create(FFuture);
|
||||
end;
|
||||
|
||||
function TFuture<T>.WaitFor: T;
|
||||
begin
|
||||
TaskManager.WaitFor(FFuture.Done);
|
||||
|
||||
+32
-19
@@ -4,23 +4,27 @@ interface
|
||||
|
||||
uses
|
||||
System.Classes,
|
||||
System.SysUtils,
|
||||
FMX.Controls,
|
||||
Myc.Signals;
|
||||
|
||||
type
|
||||
TFMXSignalLink = class(TComponent, TSignal.ISubscriber)
|
||||
TFMXValidationState = class(TComponent, TSignal.ISubscriber)
|
||||
private
|
||||
[volatile]
|
||||
FInvalidated: Integer;
|
||||
procedure InvalidateControl;
|
||||
|
||||
FSignal: TSignal.ISignal;
|
||||
FSubscription: TSignal.TSubscriptionTag;
|
||||
FProc: TProc;
|
||||
procedure CallProc;
|
||||
protected
|
||||
function Notify: Boolean;
|
||||
|
||||
public
|
||||
constructor Create(AOwner: TControl); reintroduce;
|
||||
constructor Create(AOwner: TControl; const ASignal: TSignal.ISignal; const AProc: TProc); reintroduce;
|
||||
destructor Destroy; override;
|
||||
function Reset: Boolean;
|
||||
procedure AfterConstruction; override;
|
||||
procedure BeforeDestruction; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@@ -28,36 +32,45 @@ implementation
|
||||
uses
|
||||
System.SyncObjs;
|
||||
|
||||
{ TFMXSignalLink }
|
||||
{ TFMXValidationState }
|
||||
|
||||
constructor TFMXSignalLink.Create(AOwner: TControl);
|
||||
constructor TFMXValidationState.Create(AOwner: TControl; const ASignal: TSignal.ISignal; const AProc: TProc);
|
||||
begin
|
||||
inherited Create(AOwner);
|
||||
FInvalidated := 0;
|
||||
FSignal := ASignal;
|
||||
FProc := AProc;
|
||||
end;
|
||||
|
||||
destructor TFMXSignalLink.Destroy;
|
||||
destructor TFMXValidationState.Destroy;
|
||||
begin
|
||||
TThread.RemoveQueuedEvents(InvalidateControl);
|
||||
TThread.RemoveQueuedEvents(CallProc);
|
||||
inherited;
|
||||
end;
|
||||
|
||||
procedure TFMXSignalLink.InvalidateControl;
|
||||
procedure TFMXValidationState.AfterConstruction;
|
||||
begin
|
||||
with TControl(Owner) do
|
||||
InvalidateRect(LocalRect);
|
||||
inherited;
|
||||
FSubscription := FSignal.Subscribe(Self);
|
||||
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
|
||||
if TInterlocked.Exchange(FInvalidated, 1) = 0 then
|
||||
TThread.Queue(nil, InvalidateControl);
|
||||
TThread.Queue(nil, CallProc);
|
||||
exit(true);
|
||||
end;
|
||||
|
||||
function TFMXSignalLink.Reset: Boolean;
|
||||
begin
|
||||
Result := TInterlocked.Exchange(FInvalidated, 0) > 0;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+33
-12
@@ -3,8 +3,7 @@ unit Myc.Signals;
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.SyncObjs;
|
||||
System.SysUtils;
|
||||
|
||||
type
|
||||
TSignal = record
|
||||
@@ -137,8 +136,7 @@ type
|
||||
strict private
|
||||
class var
|
||||
FNull: ILatch;
|
||||
FQueueGate: TLatch;
|
||||
FQueueLock: TSpinLock;
|
||||
FQueueLock: Integer;
|
||||
class constructor ClassCreate;
|
||||
|
||||
private
|
||||
@@ -153,8 +151,8 @@ type
|
||||
|
||||
class function CreateLatch(Count: Integer): TLatch; static;
|
||||
|
||||
class function Enqueue(var Gate: TLatch): TState; overload; static;
|
||||
class function Enqueue: TState; overload; static;
|
||||
class function Enqueue1(var Gate: TLatch): TState; overload; static;
|
||||
function Enqueue: TState; overload;
|
||||
|
||||
class property Null: ILatch read FNull;
|
||||
|
||||
@@ -394,7 +392,7 @@ class constructor TLatch.ClassCreate;
|
||||
begin
|
||||
// Create a singleton null latch instance that is initially (and always) set.
|
||||
FNull := TMycNullLatch.Create;
|
||||
FQueueLock := TSpinLock.Create(false);
|
||||
FQueueLock := 0;
|
||||
end;
|
||||
|
||||
constructor TLatch.Create(const ALatch: TLatch.ILatch);
|
||||
@@ -412,23 +410,46 @@ begin
|
||||
Result := FNull;
|
||||
end;
|
||||
|
||||
class function TLatch.Enqueue(var Gate: TLatch): TState;
|
||||
class function TLatch.Enqueue1(var Gate: TLatch): TState;
|
||||
begin
|
||||
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
|
||||
Gate.State.Subscribe(Latch);
|
||||
Gate := Latch;
|
||||
Result := Latch.State;
|
||||
finally
|
||||
FQueueLock.Exit;
|
||||
AtomicDecrement(FQueueLock);
|
||||
end;
|
||||
end;
|
||||
|
||||
class function TLatch.Enqueue: TState;
|
||||
function TLatch.Enqueue: TState;
|
||||
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;
|
||||
|
||||
function TLatch.GetState: TState;
|
||||
|
||||
+264
-269
@@ -30,34 +30,6 @@ uses
|
||||
Myc.Core.FileCache;
|
||||
|
||||
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.
|
||||
// IsHistory:
|
||||
// - 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}']
|
||||
function CreateStream(const Symbol: String): IDataStream<T>;
|
||||
procedure ClearCache;
|
||||
function EnumerateSymbols: TArray<String>;
|
||||
function EnumerateSymbols: TFuture<TArray<String>>;
|
||||
end;
|
||||
|
||||
// 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.
|
||||
TAuraDataServer<T: record> = class(TInterfacedObject, IDataServer<T>)
|
||||
private
|
||||
FCachedFiles: TDataFileCache<TArray<TDataPoint<T>>>;
|
||||
FPath: String;
|
||||
function DoLoad(const FileName: string): TFuture<TArray<TDataPoint<T>>>;
|
||||
FSymbols: TFuture<TDictionary<String, TAuraDataFile>>;
|
||||
function GetPath: String;
|
||||
|
||||
// Used by cache to actually load an uncached file.
|
||||
function DoLoad(const FileName: string): TFuture<TArray<TDataPoint<T>>>;
|
||||
|
||||
strict private
|
||||
class var
|
||||
FLoadGate: TLatch;
|
||||
|
||||
protected
|
||||
class function ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<T>>; static;
|
||||
class function ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<T>>; static;
|
||||
// Scans a directory to find the oldest file for a specific symbol.
|
||||
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
|
||||
constructor Create(const APath: String);
|
||||
destructor Destroy; override;
|
||||
procedure AfterConstruction; override;
|
||||
function CreateStream(const Symbol: String): IDataStream<T>; virtual; abstract;
|
||||
procedure ClearCache;
|
||||
function EnumerateSymbols: TArray<String>;
|
||||
function LoadDataFile(const DataFile: TDataFile): TFuture<TArray<TDataPoint<T>>>;
|
||||
procedure UpdateSymbols;
|
||||
function EnumerateSymbols: TFuture<TArray<String>>;
|
||||
function LoadDataFile(const DataFile: TAuraDataFile): TFuture<TArray<TDataPoint<T>>>;
|
||||
property Path: String read GetPath;
|
||||
end;
|
||||
|
||||
@@ -135,7 +139,7 @@ type
|
||||
private
|
||||
type
|
||||
TDataStream = record
|
||||
FileInfo: TDataFile;
|
||||
FileInfo: TAuraDataFile;
|
||||
Data: TFuture<TArray<TDataPoint<T>>>;
|
||||
end;
|
||||
private
|
||||
@@ -162,9 +166,14 @@ type
|
||||
// Aura tick data file Ask-Bid
|
||||
|
||||
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
|
||||
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;
|
||||
|
||||
implementation
|
||||
@@ -175,9 +184,9 @@ uses
|
||||
System.StrUtils,
|
||||
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
|
||||
FPath := APath;
|
||||
FSymbol := ASymbol;
|
||||
@@ -186,28 +195,28 @@ begin
|
||||
FMonth := AMonth;
|
||||
end;
|
||||
|
||||
function TDataFile.GetBaseFileName: string;
|
||||
function TAuraDataFile.GetBaseFileName: string;
|
||||
begin
|
||||
Result := Format('%s_%.4d_%.2d', [FSymbol, FYear, FMonth]);
|
||||
end;
|
||||
|
||||
function TDataFile.GetFullFileName: string;
|
||||
function TAuraDataFile.GetFullFileName: string;
|
||||
begin
|
||||
Result := TPath.Combine(FPath, GetBaseFileName + FExtension);
|
||||
end;
|
||||
|
||||
function TDataFile.GetIsValid: Boolean;
|
||||
function TAuraDataFile.GetIsValid: Boolean;
|
||||
begin
|
||||
Result := (FSymbol <> '') and (FYear > 0);
|
||||
end;
|
||||
|
||||
function TDataFile.GetNextFile: TDataFile;
|
||||
function TAuraDataFile.GetNextFile: TAuraDataFile;
|
||||
var
|
||||
nextMonth, nextYear: Integer;
|
||||
nextFile: TDataFile;
|
||||
nextFile: TAuraDataFile;
|
||||
begin
|
||||
if not IsValid then
|
||||
exit(Default(TDataFile));
|
||||
exit(Default(TAuraDataFile));
|
||||
|
||||
nextMonth := FMonth + 1;
|
||||
nextYear := FYear;
|
||||
@@ -218,137 +227,19 @@ begin
|
||||
end;
|
||||
|
||||
// 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
|
||||
exit(nextFile);
|
||||
|
||||
// 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
|
||||
exit(nextFile);
|
||||
|
||||
// No next file found
|
||||
Result := Default(TDataFile);
|
||||
Result := Default(TAuraDataFile);
|
||||
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> }
|
||||
|
||||
constructor TAuraDataServer<T>.Create(const APath: String);
|
||||
@@ -368,27 +259,17 @@ begin
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
procedure TAuraDataServer<T>.AfterConstruction;
|
||||
begin
|
||||
inherited;
|
||||
UpdateSymbols;
|
||||
end;
|
||||
|
||||
procedure TAuraDataServer<T>.ClearCache;
|
||||
begin
|
||||
FCachedFiles.Clear;
|
||||
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>>>;
|
||||
begin
|
||||
Result := TFuture<TArray<TDataPoint<T>>>.Null;
|
||||
@@ -401,9 +282,7 @@ begin
|
||||
begin
|
||||
Result :=
|
||||
TFuture<TBytes>
|
||||
.Construct(
|
||||
TLatch.Enqueue(FLoadGate),
|
||||
function: TBytes begin Result := TFile.ReadAllBytes(capFileName); end)
|
||||
.Construct(FLoadGate.Enqueue, function: TBytes begin Result := TFile.ReadAllBytes(capFileName); end)
|
||||
.Chain<TArray<TDataPoint<T>>>(
|
||||
function(bytes: TBytes): TArray<TDataPoint<T>>
|
||||
begin
|
||||
@@ -420,7 +299,7 @@ begin
|
||||
begin
|
||||
Result :=
|
||||
TFuture<TArray<TDataPoint<T>>>.Construct(
|
||||
TLatch.Enqueue(FLoadGate),
|
||||
FLoadGate.Enqueue,
|
||||
function: TArray<TDataPoint<T>>
|
||||
begin
|
||||
if TFile.Exists(capFileName) then
|
||||
@@ -442,81 +321,82 @@ begin
|
||||
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
|
||||
Result := FCachedFiles.GetOrAdd(DataFile.GetFullFileName);
|
||||
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> }
|
||||
|
||||
constructor TAuraFileStream<T>.Create(ADataServer: TAuraDataServer<T>; const ASymbol: String);
|
||||
@@ -535,18 +415,16 @@ end;
|
||||
|
||||
procedure TAuraFileStream<T>.AfterConstruction;
|
||||
var
|
||||
firstFile: TFuture<TDataFile>;
|
||||
serverPath: string;
|
||||
firstFile: TFuture<TAuraDataFile>;
|
||||
begin
|
||||
inherited;
|
||||
|
||||
// We need the path from the server instance to find the file.
|
||||
serverPath := (FDataServer as TAuraDataServer<T>).Path;
|
||||
firstFile := TFuture<TDataFile>.Construct(function: TDataFile begin Result := TDataFile.FindFirst(serverPath, FSymbol); end);
|
||||
firstFile := FDataServer.FindFirstFile(FSymbol);
|
||||
|
||||
FCurrent :=
|
||||
firstFile.Chain<TDataStream>(
|
||||
function(const FileInfo: TDataFile): TDataStream
|
||||
function(const FileInfo: TAuraDataFile): TDataStream
|
||||
begin
|
||||
if FileInfo.IsValid then
|
||||
begin
|
||||
@@ -638,7 +516,7 @@ begin
|
||||
FCurrent.Chain<TDataStream>(
|
||||
function(const Prev: TDataStream): TDataStream
|
||||
var
|
||||
nextFileInfo: TDataFile;
|
||||
nextFileInfo: TAuraDataFile;
|
||||
begin
|
||||
if Prev.FileInfo.IsValid then
|
||||
begin
|
||||
@@ -661,16 +539,16 @@ begin
|
||||
Result := TAuraFileStream<TAskBidItem>.Create(Self, Symbol);
|
||||
end;
|
||||
|
||||
function TAuraTABFileServer.LoadDataSeries(const InitialFile: TDataFile): TFuture<TArray<TDataPoint<TAskBidItem>>>;
|
||||
function TAuraTABFileServer.LoadDataSeries(const InitialFile: TAuraDataFile): TFuture<TArray<TDataPoint<TAskBidItem>>>;
|
||||
var
|
||||
loadedState: TState;
|
||||
loadedFiles: TArray<TFuture<TArray<TDataPoint<TAskBidItem>>>>;
|
||||
liveData: TFuture<TArray<TDataPoint<TAskBidItem>>>;
|
||||
tabFiles: TList<TDataFile>;
|
||||
liveFile: TDataFile;
|
||||
currentFileInfo: TDataFile;
|
||||
tabFiles: TList<TAuraDataFile>;
|
||||
liveFile: TAuraDataFile;
|
||||
currentFileInfo: TAuraDataFile;
|
||||
begin
|
||||
tabFiles := TList<TDataFile>.Create;
|
||||
tabFiles := TList<TAuraDataFile>.Create;
|
||||
try
|
||||
currentFileInfo := InitialFile;
|
||||
while currentFileInfo.IsValid do
|
||||
@@ -679,14 +557,14 @@ begin
|
||||
currentFileInfo := currentFileInfo.GetNextFile;
|
||||
end;
|
||||
|
||||
liveFile := Default(TDataFile);
|
||||
liveFile := Default(TAuraDataFile);
|
||||
if tabFiles.Count > 0 then
|
||||
begin
|
||||
var lastTabFile := tabFiles.Last;
|
||||
var liveFileBaseName := Format('%s_%d_%02d.tab-live', [lastTabFile.Symbol, lastTabFile.Year, lastTabFile.Month]);
|
||||
var potentialLivePath := TPath.Combine(lastTabFile.Path, liveFileBaseName);
|
||||
if TFile.Exists(potentialLivePath) then
|
||||
liveFile := TDataFile.ParseFileName(potentialLivePath);
|
||||
liveFile := ParseFileName(potentialLivePath);
|
||||
end;
|
||||
|
||||
var loadedFileList := TList<TFuture<TArray<TDataPoint<TAskBidItem>>>>.Create;
|
||||
@@ -745,4 +623,121 @@ begin
|
||||
);
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user