Hallo Baerbel, Hallo alle interessierten
bei mir hat folgender Ansatz in C# funktioniert:
Ich habe mir eine Class Library "TaiPan.Reader" geschrieben, die unter anderem folgende statischen Methoden enthält:
1. Konstruktion: (Hinweis: die Klasse heißt derzeit noch "Application" und ist static)
static Application()
{
Task<TaiPan> initTaiPanTask = Task<TaiPan>.Factory.StartNew(() => new TaiPanClass(), CancellationToken.None, TaskCreationOptions.None, Scheduler);
initTaiPanTask.Wait();
App = initTaiPanTask.Result;
AnzahlKataloge = App.KatalogListe.Count;
}
Das Objekt Scheduler nagelt mir alles auf das Single Threaded Apartment fest, obwohl ich TPL (async/await) benutze.
Es wird vor dem Konstruktor wie folgt initialisiert:
/// <summary>
/// This scheduler should be used exclusively for all Tasks accessing the TaiPan COM server.
/// It ensures thread-safe operations by enforcing the STA model.
/// </summary>
private static readonly StaTaskScheduler Scheduler = new StaTaskScheduler( 8 );
private static readonly TaiPan App;
Du findest den StaTaskScheduler im Paket MSFT.ParallelExtensionsExtras von Stephen Cleary, das Du Dir mit NuGet in Dein Projekt lädst.
Siehe hierzu bei Interesse
https://blogs.msdn.microsoft.com/pfxteam/2010/04/07/parallelextensionsextras-tour-5-stataskscheduler/Dann habe ich eine Methode, die mir jeweils zwei Arrays für einen bestimmten Kurstypen liefert: Die Datumswerte und die Kurse.
Das klappt nach meinen Tests in C# besser, als der Umgang mit dem SafeArray:
private static (Array tage, Array kurse) GetKursreiheArrays(string wpk, KursTyp typ)
{
var kursreihe = App.KursReihe[wpk, typ];
return kursreihe == null
? (Array.Empty<object>(), Array.Empty<object>())
: (kursreihe.Count == 0
? (Array.Empty<object>(), Array.Empty<object>())
: (kursreihe.DatumArray, kursreihe.WertArray));
}
Um jetzt eine schöne OHLCV Kursreihe für ein einzelnes Wertpapier zu bekommen, verwende ich folgendes:
public static async Task<IList<KurseOL>> GetKurseOLArrayMethodAsync(string wpk, CancellationToken cancelToken, IProgress<int> reportProgress)
{
IList<Task<(Array tage, Array kurse)>> arraysHolenTasks = new List<Task<(Array tage, Array kurse)>>(5);
var getOpenTask = Task<(Array tage, Array kurse)>.Factory.StartNew(
() => GetKursreiheArrays(wpk, KursTyp.ktEroeffnungOL), cancelToken, TaskCreationOptions.LongRunning, Scheduler);
arraysHolenTasks.Add(getOpenTask);
var getHighTask = Task<(Array tage, Array kurse)>.Factory.StartNew(
() => GetKursreiheArrays(wpk, KursTyp.ktHochOL), cancelToken, TaskCreationOptions.LongRunning, Scheduler);
arraysHolenTasks.Add(getHighTask);
var getLowTask = Task<(Array tage, Array kurse)>.Factory.StartNew(
() => GetKursreiheArrays(wpk, KursTyp.ktTiefOL), cancelToken, TaskCreationOptions.LongRunning, Scheduler);
arraysHolenTasks.Add(getLowTask);
var getCloseTask = Task<(Array tage, Array kurse)>.Factory.StartNew(
() => GetKursreiheArrays(wpk, KursTyp.ktSchlussOL), cancelToken, TaskCreationOptions.LongRunning, Scheduler);
arraysHolenTasks.Add(getCloseTask);
var getVolTask = Task<(Array tage, Array kurse)>.Factory.StartNew(
() => GetKursreiheArrays(wpk, KursTyp.ktVolumenOL), cancelToken, TaskCreationOptions.LongRunning, Scheduler);
arraysHolenTasks.Add(getVolTask);
cancelToken.ThrowIfCancellationRequested();
await Task.WhenAll(arraysHolenTasks).ConfigureAwait(false);
var open = getOpenTask.Result;
var high = getHighTask.Result;
var low = getLowTask.Result;
var close = getCloseTask.Result;
var vol = getVolTask.Result;
IDictionary<DateTime, KurseOL> result = new Dictionary<DateTime, KurseOL>();
for (int i = 0; i < close.tage.Length; i++)
{
var datum = Convert.ToDateTime(close.tage.GetValue(i));
var kurs = Convert.ToDouble(close.kurse.GetValue(i));
result.Add(datum, new KurseOL { Datum = datum, Wpk = wpk, Close = kurs });
}
for (int i = 0; i < open.tage.Length; i++)
{
var datum = Convert.ToDateTime(open.tage.GetValue(i));
var kurs = Convert.ToDouble(open.kurse.GetValue(i));
if (result.ContainsKey(datum))
{
result[datum].Open = kurs;
}
else
{
result.Add(datum, new KurseOL { Datum = datum, Wpk = wpk, Open = kurs });
}
}
for (int i = 0; i < high.tage.Length; i++)
{
var datum = Convert.ToDateTime(high.tage.GetValue(i));
var kurs = Convert.ToDouble(high.kurse.GetValue(i));
if (result.ContainsKey(datum))
{
result[datum].High = kurs;
}
else
{
result.Add(datum, new KurseOL { Datum = datum, Wpk = wpk, High = kurs });
}
}
for (int i = 0; i < low.tage.Length; i++)
{
var datum = Convert.ToDateTime(low.tage.GetValue(i));
var kurs = Convert.ToDouble(low.kurse.GetValue(i));
if (result.ContainsKey(datum))
{
result[datum].Low = kurs;
}
else
{
result.Add(datum, new KurseOL { Datum = datum, Wpk = wpk, Low = kurs });
}
}
for (int i = 0; i < vol.tage.Length; i++)
{
var datum = Convert.ToDateTime(vol.tage.GetValue(i));
var kurs = Convert.ToDouble(vol.kurse.GetValue(i));
if (result.ContainsKey(datum))
{
result[datum].Volume = kurs;
}
else
{
result.Add(datum, new KurseOL { Datum = datum, Wpk = wpk, Volume = kurs });
}
}
reportProgress?.Report(result.Count);
return result.Values.ToList();
}
Die diversen FOR Schleifen wirken redundant und ReSharper nervt auch ständig, er wolle sie in LINQ Anweisungen umwandeln, aber so ist es performanter.
Auf meinem Notebook konnte ich so etwa 120 komplette Kursreihen (OHLCV) je Sekunde auslesen, was 600x2 Arrays und etwa 350.000 einzelnen Datensätzen (bzw. 3.500.000 Arrayelementen) entsprach.
Derzeit bastele ich daran, das in annähernd demselben Tempo in meine SQL Server Datenbank zu kriegen...
Du solltest .NET 4.7 benutzen, damit z.B. die named Tuples (Array tage, Array kurse) funktionieren.
Bei Fragen komm gerne auf mich zurück.
Würde mich freuen, wenn wir hier einen Interface- / C# Entwickler Austausch hinbekämen.
EDIT: Wenn ich die Werte mit TPL Dataflow und EF weiterverarbeite, um sie auf meine Datenbank zu schreiben, geht die Geschwindigkeit ziemlich in die Knie;
habe jetzt 110 Minuten gebraucht, um 161.005 Kursreihen mit insges. 344,2 Mio Kursen in meine DB zu kopieren.
Ich werde in nächster Zeit etwas experimentieren, wie ich das beschleunigen kann, bin aber bis hier schon mal ganz zufrieden.
Was ich noch nicht so schnell hinkriege, ist das Laden der diversen Stammdaten, insbesondere der daranhängenden Arrays (EBIT, Dividenden, ...).
Wenn das mal alles passt und ich erste Backtests auf der DB hinkriege, mehr dazu hier und in einem extra Blog...