Phase 6 (Stufe 6): MongoDB vollstaendig entfernt

- Legacy-Form stillgelegt: frm_main(.Designer/.resx) + frm_analytics(.Designer/.resx)
  geloescht, 'Open Legacy'-Menue aus LauncherForm entfernt.
- Program.cs: IMongoDatabase-Registrierung + closed_trades-Cleanup weg;
  Trade-Nummerierung liest jetzt hoechste TradeId aus ICopyTradeLogRepository.
- StartupHydrationService: Mongo-/Bson-Fallback entfernt (Settings kommen aus
  dem Repo; fehlende Accounts erhalten Default-Settings).
- Alle Mongo*Repository (Core 4 + Modul 4) geloescht; DI-Zweige auf EF/MySQL
  reduziert (AddCorePersistence, CopyTradingModule).
- ConfigMigrator: Mongo-Pfad (Run/MigrateSettings) entfernt; nur noch
  RunFromJson + VerifyMySql (kein Mongo-Treiber mehr).
- Modelle: [BsonId] entfernt (EF-Keys via HasKey); ObjectId-Defaults -> Guid.
- LiteDB-Shim (MongoDbLiteDBShim) geloescht; MongoDB.Driver- + LiteDB-Pakete
  aus App/Core entfernt; stale usings + user-sichtbare Strings bereinigt.
- DatabaseOptions/appsettings.json: Mongo-Provider/Connection-Felder entfernt.
- Build gruen; --verify-mysql bestaetigt 3 Accounts/3 Settings/32 Trader/2344 Markets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-05 18:34:17 +02:00
co-authored by Claude Opus 4.8
parent d227d24ca8
commit b951b912f4
38 changed files with 48 additions and 5681 deletions
-93
View File
@@ -1,93 +0,0 @@
using System;
using MongoDB.Driver;
using PolyTraderSharp.Extensions;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using PolyTraderSharp.Models;
namespace PolyTraderSharp.Extensions
{
public static class MongoDbLiteDBShim
{
// 1. LiteDB: FindOne(predicate) -> MongoDB: Find(predicate).FirstOrDefault()
public static T LiteFindOne<T>(this IMongoCollection<T> col, Expression<Func<T, bool>> predicate)
{
return col.Find(predicate).FirstOrDefault();
}
// 2. LiteDB: Find(predicate) -> MongoDB: Find(predicate).ToList()
// Note: LiteDB returns IEnumerable<T>. ToList() is perfectly fine for iteration.
public static List<T> LiteFind<T>(this IMongoCollection<T> col, Expression<Func<T, bool>> predicate)
{
return col.Find(predicate).ToList();
}
// 3. LiteDB: FindAll() -> MongoDB: Find(_ => true).ToList()
public static List<T> LiteFindAll<T>(this IMongoCollection<T> col)
{
return col.Find(_ => true).ToList();
}
// 4. Upsert extensions mapped to Primary Keys
public static void Upsert(this IMongoCollection<AccountState> col, AccountState doc)
{
col.ReplaceOne(x => x.AccountId == doc.AccountId, doc, new ReplaceOptions { IsUpsert = true });
}
public static void Upsert(this IMongoCollection<TrackedTrader> col, TrackedTrader doc)
{
col.ReplaceOne(x => x.Id == doc.Id, doc, new ReplaceOptions { IsUpsert = true });
}
public static void Upsert(this IMongoCollection<Position> col, Position doc)
{
col.ReplaceOne(x => x.TokenId == doc.TokenId, doc, new ReplaceOptions { IsUpsert = true });
}
public static void Upsert(this IMongoCollection<MarketData> col, MarketData doc)
{
col.ReplaceOne(x => x.Id == doc.Id, doc, new ReplaceOptions { IsUpsert = true });
}
// 5. Update (Updates without upserting if it doesn't exist)
public static void Update(this IMongoCollection<TrackedTrader> col, TrackedTrader doc)
{
col.ReplaceOne(x => x.Id == doc.Id, doc);
}
public static void Update(this IMongoCollection<AccountState> col, AccountState doc)
{
col.ReplaceOne(x => x.AccountId == doc.AccountId, doc);
}
public static void Update(this IMongoCollection<MarketData> col, MarketData doc)
{
col.ReplaceOne(x => x.Id == doc.Id, doc);
}
// 6. Insert maps perfectly to InsertOne
public static void Insert<T>(this IMongoCollection<T> col, T doc)
{
col.InsertOne(doc);
}
// 7. Delete (by TokenId / String ID)
public static void Delete(this IMongoCollection<Position> col, string tokenId)
{
col.DeleteOne(x => x.TokenId == tokenId);
}
// 8. EnsureIndex shim (MongoDB Index Creation)
public static void EnsureIndex<T>(this IMongoCollection<T> col, Expression<Func<T, object>> property)
{
try
{
var indexKeys = Builders<T>.IndexKeys.Ascending(property);
var indexModel = new CreateIndexModel<T>(indexKeys);
col.Indexes.CreateOne(indexModel);
}
catch { }
}
}
}
-2
View File
@@ -59,8 +59,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="LiteDB" Version="5.0.21" />
<PackageReference Include="MongoDB.Driver" Version="2.24.0" />
<PackageReference Include="Nethereum.Web3" Version="6.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
+7 -64
View File
@@ -1,6 +1,5 @@
using System;
using MongoDB.Driver;
using PolyTraderSharp.Extensions;
using System.Linq;
using System.Net.Http;
using System.Threading.Channels;
using System.Windows.Forms;
@@ -26,13 +25,6 @@ internal static class Program
[STAThread]
private static void Main(string[] args)
{
// Einmalige Config-Migration Mongo -> MySQL (Phase 6, Stufe 4). Kein UI-Start.
if (args.Length > 0 && string.Equals(args[0], "--migrate-config", StringComparison.OrdinalIgnoreCase))
{
RunConfigMigration();
return;
}
// Config-Migration aus mongoexport-JSON (Ordner, Default "MongoDB") -> MySQL.
if (args.Length > 0 && string.Equals(args[0], "--migrate-json", StringComparison.OrdinalIgnoreCase))
{
@@ -59,18 +51,9 @@ internal static class Program
{
var databaseOptions = new DatabaseOptions
{
Provider = context.Configuration["Database:Provider"] ?? "Mongo",
MySqlConnectionString = context.Configuration["Database:MySqlConnectionString"] ?? string.Empty,
ConnectionString = context.Configuration["Database:ConnectionString"] ?? "mongodb://localhost:27017",
DatabaseName = context.Configuration["Database:DatabaseName"] ?? "PolyTraderDB"
MySqlConnectionString = context.Configuration["Database:MySqlConnectionString"] ?? string.Empty
};
services.Configure<DatabaseOptions>(context.Configuration.GetSection(DatabaseOptions.SectionName));
services.AddSingleton<IMongoDatabase>((IServiceProvider sp) =>
{
var dbOptions = sp.GetRequiredService<IOptions<DatabaseOptions>>().Value;
var client = new MongoClient(dbOptions.ConnectionString);
return client.GetDatabase(dbOptions.DatabaseName);
});
services.AddCorePersistence(databaseOptions);
services.AddSingleton<IBlockchainWssClientFactory, AlchemyWssClientFactory>();
services.AddSingleton((IServiceProvider sp) => ServerSettings.Load("server_settings.xml"));
@@ -124,36 +107,18 @@ internal static class Program
services.AddHostedService<PolymarketWssClient>();
services.AddHostedService((IServiceProvider sp) => sp.GetRequiredService<MullvadVpnService>());
services.AddHostedService((IServiceProvider sp) => sp.GetRequiredService<ThreemaService>());
services.AddTransient<frm_main>();
services.AddSingleton<PolyTraderSharp.Ui.ShellUiHost>();
services.AddTransient<PolyTraderSharp.Ui.LauncherForm>();
}).Build();
try
{
var db = AppHost.Services.GetRequiredService<IMongoDatabase>();
// Einmal-Bereinigung (Alt-Bug): entferne closed_trades mit ObjectId-_id.
// Das _id muss die int-TradeId sein; fehlerhafte Auto-ObjectIds werden verworfen.
// Läuft jetzt über die konfigurierte DB statt über einen hart codierten Client.
try
{
var cleanupCol = db.GetCollection<MongoDB.Bson.BsonDocument>("closed_trades");
cleanupCol.DeleteMany(Builders<MongoDB.Bson.BsonDocument>.Filter.Type("_id", MongoDB.Bson.BsonType.ObjectId));
}
catch { }
// Trade-Nummerierung fortsetzen: höchste bestehende TradeId aus dem Log lesen.
var copyState = AppHost.Services.GetRequiredService<CopyTradingState>();
var maxTradeDoc = db.GetCollection<MongoDB.Bson.BsonDocument>("closed_trades")
.Find(Builders<MongoDB.Bson.BsonDocument>.Filter.Empty)
.SortByDescending(d => d["_id"])
.Limit(1)
.FirstOrDefault();
if (maxTradeDoc != null && maxTradeDoc.Contains("_id"))
{
copyState.TotalCopyTrades = maxTradeDoc["_id"].AsInt32;
}
var tradeLog = AppHost.Services.GetRequiredService<ICopyTradeLogRepository>();
var allTrades = tradeLog.Find(_ => true);
if (allTrades.Count > 0)
copyState.TotalCopyTrades = allTrades.Max(t => t.TradeId);
}
catch { }
@@ -232,28 +197,6 @@ internal static class Program
AppHost.StopAsync().GetAwaiter().GetResult();
}
private static void RunConfigMigration()
{
var config = new Microsoft.Extensions.Configuration.ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile("appsettings.Local.json", optional: true)
.AddEnvironmentVariables()
.Build();
var mongoConn = config["Database:ConnectionString"] ?? "mongodb://localhost:27017";
var mongoDbName = config["Database:DatabaseName"] ?? "PolyTraderDB";
var mySql = config["Database:MySqlConnectionString"] ?? string.Empty;
if (string.IsNullOrWhiteSpace(mySql))
{
Console.WriteLine("FEHLER: Database:MySqlConnectionString fehlt (appsettings.Local.json).");
return;
}
Services.ConfigMigrator.Run(mongoConn, mongoDbName, mySql);
}
private static void RunConfigMigrationFromJson(string folder)
{
var config = new Microsoft.Extensions.Configuration.ConfigurationBuilder()
+1 -9
View File
@@ -19,7 +19,6 @@ namespace PolyTraderSharp.Ui
{
menuStrip = new MenuStrip();
miDatei = new ToolStripMenuItem();
miLegacy = new ToolStripMenuItem();
miBeenden = new ToolStripMenuItem();
toolstrip_windows = new ToolStrip();
btn_dashboard = new ToolStripButton();
@@ -51,17 +50,11 @@ namespace PolyTraderSharp.Ui
//
// miDatei
//
miDatei.DropDownItems.AddRange(new ToolStripItem[] { miLegacy, miBeenden });
miDatei.DropDownItems.AddRange(new ToolStripItem[] { miBeenden });
miDatei.Name = "miDatei";
miDatei.Size = new Size(69, 29);
miDatei.Text = "Datei";
//
// miLegacy
//
miLegacy.Name = "miLegacy";
miLegacy.Size = new Size(275, 34);
miLegacy.Text = "Legacy-UI (alte Tabs)";
//
// miBeenden
//
miBeenden.Name = "miBeenden";
@@ -202,7 +195,6 @@ namespace PolyTraderSharp.Ui
private System.Windows.Forms.MenuStrip menuStrip;
private System.Windows.Forms.ToolStripMenuItem miDatei;
private System.Windows.Forms.ToolStripMenuItem miLegacy;
private System.Windows.Forms.ToolStripMenuItem miBeenden;
private ToolStrip toolstrip_windows;
private ToolStripButton btn_settings;
-19
View File
@@ -21,7 +21,6 @@ namespace PolyTraderSharp.Ui
private readonly TradingState _state;
private readonly System.Windows.Forms.Timer _statusTimer = new() { Interval = 1000 };
private readonly Dictionary<string, ToolStripButton> _viewButtons;
private frm_main? _legacyForm;
public LauncherForm(ShellUiHost uiHost, IServiceProvider services)
{
@@ -47,7 +46,6 @@ namespace PolyTraderSharp.Ui
btn_liveTrading.Click += (_, _) => CycleLiveTrading();
btn_demoTrading.Click += (_, _) => CycleDemoTrading();
miLegacy.Click += (_, _) => OpenLegacy();
miBeenden.Click += (_, _) => Close();
// Offen-Status der Fenster spiegeln (Button „checked", wenn Fenster offen).
@@ -126,25 +124,8 @@ namespace PolyTraderSharp.Ui
lbl_ratelimit.Text = $"API: {(_state.IsAlchemyHealthy ? "WSS aktiv" : "Polling")}";
// Toggle-Buttons mit externem State synchron halten (z.B. Änderungen aus der Legacy-UI).
UpdateTradingToggles();
UpdateWindowButtonStates();
}
private void OpenLegacy()
{
if (_legacyForm != null && !_legacyForm.IsDisposed)
{
if (_legacyForm.WindowState == FormWindowState.Minimized)
_legacyForm.WindowState = FormWindowState.Normal;
_legacyForm.BringToFront();
_legacyForm.Activate();
return;
}
_legacyForm = _services.GetRequiredService<frm_main>();
_legacyForm.FormClosed += (_, _) => _legacyForm = null;
_legacyForm.Show();
}
}
}
+2 -3
View File
@@ -1,7 +1,6 @@
{
"Database": {
"Provider": "MySql",
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "PolyTraderDB"
"_comment": "MySqlConnectionString liegt in der gitignorierten appsettings.Local.json.",
"MySqlConnectionString": ""
}
}
-111
View File
@@ -1,111 +0,0 @@
namespace PolyTraderSharp
{
partial class frm_analytics
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
menuStrip1 = new MenuStrip();
toolStrip1 = new ToolStrip();
tabControl1 = new TabControl();
tabPage1 = new TabPage();
tabPage2 = new TabPage();
tabControl1.SuspendLayout();
SuspendLayout();
//
// menuStrip1
//
menuStrip1.ImageScalingSize = new Size(24, 24);
menuStrip1.Location = new Point(0, 0);
menuStrip1.Name = "menuStrip1";
menuStrip1.Size = new Size(2229, 24);
menuStrip1.TabIndex = 0;
menuStrip1.Text = "menuStrip1";
//
// toolStrip1
//
toolStrip1.ImageScalingSize = new Size(24, 24);
toolStrip1.Location = new Point(0, 24);
toolStrip1.Name = "toolStrip1";
toolStrip1.Size = new Size(2229, 25);
toolStrip1.TabIndex = 1;
toolStrip1.Text = "toolStrip1";
//
// tabControl1
//
tabControl1.Controls.Add(tabPage1);
tabControl1.Controls.Add(tabPage2);
tabControl1.Location = new Point(0, 52);
tabControl1.Name = "tabControl1";
tabControl1.SelectedIndex = 0;
tabControl1.Size = new Size(2229, 1115);
tabControl1.TabIndex = 2;
//
// tabPage1
//
tabPage1.Location = new Point(4, 34);
tabPage1.Name = "tabPage1";
tabPage1.Padding = new Padding(3);
tabPage1.Size = new Size(2221, 1077);
tabPage1.TabIndex = 0;
tabPage1.Text = "tabPage1";
tabPage1.UseVisualStyleBackColor = true;
//
// tabPage2
//
tabPage2.Location = new Point(4, 34);
tabPage2.Name = "tabPage2";
tabPage2.Padding = new Padding(3);
tabPage2.Size = new Size(2221, 1077);
tabPage2.TabIndex = 1;
tabPage2.Text = "tabPage2";
tabPage2.UseVisualStyleBackColor = true;
//
// frm_analytics
//
AutoScaleDimensions = new SizeF(10F, 25F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(2229, 1169);
Controls.Add(tabControl1);
Controls.Add(toolStrip1);
Controls.Add(menuStrip1);
MainMenuStrip = menuStrip1;
Name = "frm_analytics";
Text = "Analyse";
tabControl1.ResumeLayout(false);
ResumeLayout(false);
PerformLayout();
}
#endregion
private MenuStrip menuStrip1;
private ToolStrip toolStrip1;
private TabControl tabControl1;
private TabPage tabPage1;
private TabPage tabPage2;
}
}
-22
View File
@@ -1,22 +0,0 @@
using System;
using MongoDB.Driver;
using PolyTraderSharp.Extensions;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace PolyTraderSharp
{
public partial class frm_analytics : Form
{
public frm_analytics()
{
InitializeComponent();
}
}
}
-126
View File
@@ -1,126 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>175, 17</value>
</metadata>
</root>
-2471
View File
File diff suppressed because it is too large Load Diff
-1858
View File
File diff suppressed because it is too large Load Diff
-371
View File
@@ -1,371 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>175, 17</value>
</metadata>
<metadata name="toolStrip3.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>1615, 17</value>
</metadata>
<metadata name="col_mastername.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="col_aktproz.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="col_aktUSD.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="col_name.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="col_Winrate.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="col_pl.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="col_trades.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="dataGridViewLinkColumn1.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="dataGridViewTextBoxColumn1.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="dataGridViewTextBoxColumn2.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="dataGridViewTextBoxColumn3.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Column_Laufzeit.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Column_LaufzeitProz.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Column_laufzeitUSD.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Column_Aktuellproz.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Column_aktuellusd.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="toolStrip_openTrades.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>625, 17</value>
</metadata>
<metadata name="toolStrip_closedtrades.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>770, 17</value>
</metadata>
<metadata name="toolStrip4.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>1760, 17</value>
</metadata>
<metadata name="imageList_tabpages.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>1905, 17</value>
</metadata>
<data name="imageList_tabpages.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAfh0AAAJNU0Z0AUkBTAIBAQUB
AAEoAQABKAEAARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAASADAAEBAQABIAYAASAS
AAMEAQUDIwEzAywBQwMsAUMDLAFDAywBQwMsAUMDLAFDAywBQwMsAUMDLAFDAywBQwMsAUMDLAFDAysB
QQMSARjAAAMQARUDyQH/AfcC9gH/AfgC9wH/AfgC9wH/AfgC9wH/AfgC9wH/AfgC9wH/AfgC9wH/AfgC
9wH/AfcC9gH/AfYC9QH/AfYC9QH/AfYC9QH/AfkC+AH/A20B38AAAxEBFgH5AvgB/wHzAfIB8QH/AXEB
bQFrAf8BcQFtAWsB/wFxAW0BawH/AXEBbQFrAf8BcQFtAWsB/wFxAW0BawH/AXEBbQFrAf8B8wHyAfEB
/wHrAeoB6QH/AesB6gHpAf8B6wHqAekB/wHsAesB6gH/A5gB/8AAAxEBFgH6AvkB/wHyAfAB8QH/AW8B
bAFqFf8BbwFsAWoB/wHyAvEB/wHnAuUB/wHnAuUB/wHnAuUB/wHnAuUB/wOYAf/AAAMRARYB/AL7Af8B
7gLsAf8BaQFmAWQB/wHlAuMB/wHjAuEB/wHjAuEB/wHjAuEB/wHlAuMB/wFpAWYBZAH/Ae4C7QH/AeMC
4QH/AeMC4QH/AeMC4QH/AeMC4QH/A5gB/8AAAxEBFgP+Af8B6QHoAecB/wFlAWMBYQH/AeAB3wHeAf8B
3gHdAdwB/wHeAd0B3AH/Ad4B3QHcAf8B4AHfAd4B/wFlAWMBYQH/AfIB8QHwAf8B6QHoAecB/wHpAegB
5wH/AeYB5QHkAf8B3gHdAdsB/wOYAf/AAAMRARYE/wHlAeQB4wH/AWEBXwFdAf8B3AHbAdoB/wHaAdkB
2AH/AdoB2QHYAf8B2gHZAdgB/wHcAdsB2gH/AWIBYAFdAf8BYwFhAV8B/wFeAVwBWgH/AWIBYAFeAf8B
YQFfAVwB/wHaAdkB2AH/A5gB/8AAAxEBFgT/AeEB3wHeAf8BXQFbAVkB/wHYAdYB1QH/AdYB1AHTAf8B
1gHUAdMB/wHWAdQB0wH/AdgB1gHVAf8BXQFbAVkB/wHhAd8B3gH/AdUB0wHSAf8B4QHfAd4B/wFdAVsB
WQH/AdYB1AHTAf8DmAH/wAADEQEWBP8B3QHbAdoB/wFdAVsBWgH/Ab4BvAG7Af8BvQG7AboB/wG9AbsB
ugH/Ab0BuwG6Af8BvgG8AbsB/wFdAVsBWgH/Ad0B2wHaAf8B0gHQAc8B/wHdAdsB2gH/AVkBVwFWAf8B
0QHPAc4B/wGXApgB/8AAAxEBFgT/AdQB0gHQAf8BUQFPAU4B/wFSAU8BTgH/AVIBUAFPAf8BXAFaAVkB
/wFWAVMBUgH/AVIBTwFOAf8BUQFPAU4B/wHVAdMB0QH/Ac4BzAHKAf8B2QHXAdUB/wFVAVMBUgH/Ac0B
ygHIAf8BlwKYAf/AAAMRARYE/wHIAcUBxAH/AcgBxQHEAf8ByAHFAcQB/wHKAccBxgH/AVEBTgFNAf8B
1AHQAc8B/wHIAcUBxAH/AcgBxQHEAf8ByQHGAcUB/wHKAccBxgH/AdUB0gHRAf8BUgFPAU4B/wHIAcUB
xAH/ApcBmAH/wAADEQEWBP8BxQHCAcEB/wHGAcMBwgH/AcYBwwHCAf8ByAHFAcQB/wFOAUsBSgH/AdQB
0QHQAf8BygHHAcYB/wHKAccBxgH/AcoBxwHGAf8BygHHAcYB/wHUAdEB0AH/AU4BSwFKAf8BxAHAAb8B
/wOXAf/AAAMRARYE/wHAAb4BvAH/AcEBvwG9Af8BwQG/Ab0B/wHDAcEBvwH/AUkCRwH/AU4BTAFLAf8B
SwFJAUgB/wFLAUkBSAH/AUsBSQFIAf8BSwFJAUgB/wFOAUwBSwH/AUkCRwH/Ab4BvAG6Af8DlwH/wAAD
EQEWBP8BvAG6AbgB/wG9AbsBuQH/Ab0BuwG5Af8BvQG7Abkh/wG4AbYBswH/A5cB/8AAAwwBDwT/AbIB
rwGsAf8BswGwAa4B/wGzAbABrgH/AbMBsAGtAf8BsgGvAawB/wGyAa4BrAH/AbIBrgGsAf8BsgGuAawB
/wGyAa4BrAH/AbIBrgGsAf8BsgGuAawB/wGyAa8BrAH/AbUBswGxAf8DmAH/xAADagHXA5cB/wOXAf8D
lwH/A5cB/wOXAf8DlwH/A5cB/wOXAf8DlwH/A5cB/wOXAf8DlwH/A5gB/wMEAQXAAAMMARADKwFCAywB
QwMsAUMDLAFDAywBQwMsAUMDLAFDAywBQwMsAUMDLAFDAywBQwMsAUMDLAFDAywBQwMiATEDBAEFAxEB
FgMRARYDEQEWAxEBFgMRARYDEQEWAxEBFgMRARYDEQEWAxEBFgMRARYDEQEWAxEBFgMRARYDCgENBAAD
BAEFAxABFQMPARQDAgEDIAADCgENAwoBDSgAAwYBCAMgAS4DKwFCAysBQgMgAS4DBwEJBAADEQEWOP8B
jwFxAVYB/wMRARYBtwF/AQ8B/wG2AXwBCQH/AbYBfAEJAf8BtgF8AQkB/wG2AXwBCQH/AbYBfAEJAf8B
tgF8AQkB/wG2AXwBCQH/AbYBfAEJAf8BtgF8AQkB/wG2AXwBCQH/AbYBfAEJAf8BtgF8AQkB/wG2AX0B
CgH/AWIBXQFVAcQEAAMjATMBtwGDARkB/wG4AYUBHAH/AyMBMgMCAQMYAAMPARQBYQFdAVYBxQFhAV0B
VgHFAw8BFAMKAQ0DKgE/AywBQwMsAUMDLAFDAywBQwMsAUMDLAFDAywBQwNEAXkBrwGuAa0B/wHcAdsB
3AH/AdwB2wHcAf8BrwGuAa0B/wNEAXcDBwEJAxEBFgT/AWQBxwGTAf8BZwHIAZQB/wFnAcgBlAH/AWcB
yAGUAf8BZwHIAZQB/wFnAcgBlAH/AWcByAGUAf8BZgHIAZQB/wFlAcgBkwH/AWUBxwGTAf8BZQHIAZMB
/wFmAcgBlAH/AVoBxgGNAf8BjgFtAVMB/wMRARYB3AHiAesB/wPQAf8D0AH/A9AB/wPQAf8D0AH/A9AB
/wPQAf8D0AH/A9AB/wPQAf8D0AH/A9AB/wPQAf8BtgF9AQoB/wQAAcABjgEqAf8B5QGsAU4B/wHlAaoB
SQH/AbkBhgEdAf8DIQEwBAIQAAMPARQBXgFcAVYBwQHkAaQBPAH/AegBsgFXAf8BXwFbAVYBwAMRARYB
UAGFAawB/wFLAYABqgH/AUoBfwGqAf8BSgF/AaoB/wFKAX8BqgH/AUoBfwGqAf8BSgF/AaoB/wFHAX0B
qQH/AcYBwgG/Af8BrQGrAagJ/wGtAasBqAH/AcMBwQG/Af8DIAEuAxEBFgT/AVkBuQGFAf8BXAG7AYgB
/wFdAbsBiAH/AV0BuwGIAf8BXAG7AYcB/wFaAboBhgH/AVUBuAGCAf8BigHPAasB/wHRAe4B3wH/AdEB
6gHbAf8ByQHqAdkB/wGKAc8BqwH/AUgBtAF7Af8BjgFtAVMB/wMRARYE/wFqAWcBZgH/AWwBaAFnAf8B
bAFoAWcB/wFsAWgBZwH/AWwBaAFnAf8BbAFoAWcB/wFsAWgBZwH/AWwBaAFnAf8BbAFoAWcB/wFsAWgB
ZwH/AWwBaAFnAf8BbAFoAWcB/wFjAmAB/wG0AXoBBwH/BAABugGGAR0B/wHsAboBawH/AeUBrgFRAf8B
5QGqAUkB/wG5AYYBHgH/AxwBJwQCCAADDwEUAV4BXAFWAcEB5AGkATsB/wHkAa0BUQL/AecBxAH/AVoB
WAFVAbMDEQEWAV0BjQG1Af8BkwGkAa4B/wG5AbMBrQH/AbgBswGsAf8BuAGzAawB/wG4AbMBrAH/AbkB
swGtAf8BnQGXAZMB/wGoAaUBowH/Av4B/QP/Af4D/wH+Af8C/gH9Af8BqAGlAaMB/wGPAY4BjQH5AxEB
FgT/AakB2AG+Af8BdwHAAZgB/wFmAbgBiwH/AWYBuAGLAf8BgAHFAZ8B/wHNAegB2QH/AZgBugGTAf8B
QwGFAU8B/wFCAb0BzAH/AUEBxAHZAf8BQQG3Ab4B/wFDAYUBTwH/AbIB0gG2Af8BjgFuAVMB/wMRARYE
/wFxAm4B/wFzAnAB/wFzAnAB/wFzAnAB/wFzAnAB/wFzAnAB/wFzAnAB/wFzAnAB/wFzAnAB/wFzAnAB
/wFzAnAB/wFzAnAB/wFlAWMBZQH/AbQBegEHAf8EAAMDAQQB6QHBAYIB/wHsAboBawH/AeUBrgFRAf8B
5gGqAUkB/wG3AYQBGwH/AxgBIQQBAwoBDQFeAVwBVgHBAeQBowE7Af8B5AGtAVEC/wHnAcMB/wFXAVYB
VAGuBAADEQEWAWkBmQG+Af8BuAGzAawL/wH+A/8B/gX/AZkBlQGTAf8BnwGcAZoB/wNPAf8DVwH/A1oB
/wKnAagB/wGgAZ0BmwH/AaIBngGcAf8DEQEWBP8BPQGfAZQB/wFFAYEBQgH/AUUBfAE1Af8BRQF8ATQB
/wFEAYcBUAH/AT0BtQHLAf8BOwHNAv8BPQHLAv8BPQHJAv8BPQHJAv8BPQHJAv8BPQHLAv8BKgHKAv8B
jwFuAVIB/wMRARYE/wFrAWkBaAH/AW0BawFqAf8BbQFrAWoB/wFtAWsBagH/AW0BawFqAf8BbQFrAWoB
/wFtAWsBagH/AW0BawFqAf8BbQFrAWoB/wFtAWsBagH/AW0BawFqAf8BbQFrAWoB/wFfAV0BXgH/AbQB
egEHAf8IAAMDAQQB4wG5AXcB/wHsAbsBbAH/AeUBrgFRAf8B5gGqAUkB/wG3AYQBGwH/AxkBIgMjATMB
/wHyAcoB/wHjAawBTQL/AecBwwH/AVcBVgFUAa4IAAMRARYBdgGiAccB/wG5AbIBrAH/AQABfwE+Af8B
AAGAAUAF/wPCAf8DwAH/AZ8BnQGaAf8BpwGjAaEB/wHyAfEB8AH/AfYB9QH0Af8B9gH1AfQB/wHyAfEB
8AH/AacBpAGiAf8BmgKYAfgDEQEWBP8BMwG9Av8BNgG+Av8BNgG+Av8BNgG+Av8BNgG+Av8BNgG9Av8B
NgG9Av8BNQG9Av8BNAG8Av8BMwG8Av8BNAG8Av8BNQG9Av8BJAG5Av8BkAFuAVEB/wMRARYE/wFkAWIB
YQH/AWcBZQFkAf8BZwFlAWQB/wFnAWUBZAH/AWcBZQFkAf8BZwFlAWQB/wFnAWUBZAH/AWcBZQFkAf8B
ZwFlAWQB/wFnAWUBZAH/AWcBZQFkAf8BZwFlAWQB/wFYAlcB/wG0AXoBBwH/DAAEAgHUAaYBVAH/Ae0B
uwFtAf8B5QGuAVEB/wHmAaoBSQH/AbwBhQEUAf8BpAGmAawB/wG1AZABSAL/AewBygH/AVcBVgFUAa4M
AAMRARYBgwGtAc4B/wG3AbIBqwL/AfoB/AL/AfsB/gH/AfwB+QH6Af8D+AH/A/gB/wH4AfcB+AH/AdgB
1wHWAf8BogGgAZ4B/wHwAe8B7gH/AfAB7wHuAf8BogGgAZ4B/wHaAdkB2AH/AwcBCQMRARYE/wEqAbAC
/wEuAbIC/wEuAbIC/wEuAbIC/wEuAbIC/wEsAbEC/wElAa8C/wFoAcoC/wHFAewC/wHEAewC/wG6AegC
/wFoAcoC/wEVAasC/wGPAW4BUQH/AxEBFgT/AV4BXAFbAf8BYQFfAV4B/wFhAV8BXgH/AWEBXwFeAf8B
YQFfAV4B/wFhAV8BXgH/AWEBXwFeAf8BYQFfAV4B/wFhAV8BXgH/AWEBXwFeAf8BYQFfAV4B/wFhAV8B
XgH/AVEBUAFRAf8BtAF6AQcB/wgAAwoBDQMKAQ0EAQHFAZMBNAH/Ae4BvAFuAf8B5QGuAVEB/wHmAaoB
SAH/AbMBhQEpAf8B2wHeAeMB/wMCAQMQAAMRARYBkAG3AdcB/wG4AbIBqwH/AQABgAE/Af8BAAGCAUIB
/wH9AvkB/wLBAcAB/wLAAb8B/wL5AfgB/wG+Ab0BvAH/AdEB0AHOAf8B8wLyAf8B9ALyAf8C1AHRAf8D
MQFNBAADEQEWBP8BlgHWAv8BWAG8Av8BRAGyAv8BRAGzAv8BYgHAAv8BwQHnAv8BYQGxAecB/wEAAXUB
vwH/AaYBqwGKAf8BuAGxAYcB/wGSAaUBkAH/AQABdAG/Af8BjgHNAfkB/wGPAW8BUgH/AxEBFgT/AVgC
VgH/AVICUAH/AVgCVgH/AVsCWQH/AVsCWQH/AVsCWQH/AVsCWQH/AVsCWQH/AVsCWQH/AVsCWQH/AVsC
WQH/AVsCWQH/AUsBSQFLAf8BtAF6AQcB/wQAAw8BFANdAcUDXQHFAwYBBwMGAQcBwgGNASQB/wHvAb0B
bgH/AeUBrgFRAf8B5wGpAUMB/wMnAToDEAEVAxIBGAMPARQDAwEEBAADEQEWAZsBvwHcAf8BtwGxAaoB
/wH4AfIB9AH/Af4B9AH3Af8B9gHxAfMB/wHyAvEB/wHxAvAB/wHxAvAB/wHxAfAB8QX/AUIBhgG9Af8B
LAFjAZUB/wwAAxEBFgT/AVcBjgGdAf8BAAFqAc0B/wEAAWoBzAH/AQABagHMAf8BAAF1AcAB/wGhAaUB
hQH/AfcBvwFpAf8B7QG8AW0B/wHqAbsBbwH/AekBuwFvAf8B6gG7AW8B/wHtAb0BbQH/AfQBugFfAf8B
jgFvAVQB/wMRARYE/wFRAk8B/wGRAo8B/wGPAo0B/wFSAlAB/wFVAlMB/wFVAlMB/wFVAlMB/wFVAlMB
/wFVAlMB/wFVAlMB/wFVAlMB/wFVAlMB/wFEAUMBRQH/AbQBewEIAf8DCgENA1wBwQHCAcEBvgH/AZoB
lwGVAf8DPQFpAz0BaQGkAaUBqgH/AbYBiAEsAf8B7QG6AWcB/wG7AZcBUAH/AaUCpAH/AXUBcwFxAfYB
hAGCAYAB/wF3AXUBcwH/AygBPAMFAQYDEQEWAagByAHjAf8BuAGxAaoB/wEAAYEBQAH/AQABhAFEAf8B
9gLxAf8CvgG9Af8B8gHxAfAB/wG+Ab8BvQH/ArwBuwX/AUsBjAHCAf8BPQFtAZkB/wwAAxEBFgH7A/8B
5QGzAWMB/wHmAbQBZQH/AecBtAFkAf8B5wG0AWQB/wHmAbQBZQH/AeQBswFmAf8B4wGzAWYB/wHjAbMB
ZQH/AeMBsgFkAf8B4wGyAWQB/wHjAbIBZAH/AeMBswFlAf8B4wGuAVoB/wGNAW8BVAH/AxEBFgT/AUsC
SQH/AY0CiwH/AYwCiQH/AUwCSgH/AU8CTQH/AU8CTQH/AU8CTQH/AU8CTQH/AU8CTQH/AU8CTQH/AU8C
TQH/AU8CTQH/AT4BPAE+Af8BtAF7AQgB/wMKAQ0E/wHAAb4BvQH/AZ4BnQGcAf8BtwG1AbMB/wGwAa0B
qwH/AZIBkAGOAf8B3AHeAeAB/wMGAQcBxgHFAcgB/wHDAcEBvwH/AcQBwgHAAf8BzgHMAcoB/wHdAdsB
2gH/AcgCxgH/AwwBEAMRARYBswHSAekB/wG2AbABqQH/AfAC6wH/AfYB7gHwAf8B7wLrAf8C6gHpAf8C
6gHpAf8B6gHrAekB/wHpAeoB6AX/AU4BkAHIAf8BPgFwAZoB/wwAAxEBFgH7A/8B3QGpAVgB/wHeAasB
WwH/Ad4BqwFcAf8B3gGrAVwB/wHeAasBWwH/Ad0BqQFZAf8B3QGnAVQB/wHpAcMBigH/AfkB6QHSAf8B
+QHoAdEB/wH4AeUBygH/AekBwwGKAf8B3AGjAUkB/wGNAW8BVAH/AxEBFgT/AUICQAH/ATwCOgH/AUMC
QQH/AUYCRAH/AUYCRAH/AUYCRAH/AUYCRAH/AUYCRAH/AUYCRAH/AUYCRAH/AUYCRAH/AUYCRAH/ATQB
MwE1Af8BtAF7AQgB/wQAAwIBAwGzAbEBsAH/AdABzgHNAf8BwwHBAcAB/wG2AbQBsgH/Ab8BvQG7Af8D
EAEVBAABbQJsAeYBvQG6AbkB/wG4AbYBtAH/AY4BiwGKAf8MAAMRARYBvgHaAe8B/wG3AbABqQH/AQAB
gwFCAf8BAAGGAUYB/wHvAuoB/wK7AboB/wKcAZ0B/wK8AbsB/wK6AbkF/wFPAZMBzAH/AT8BcAGcAf8M
AAMRARYE/wHwAdIBqgH/AeQBuQF6Af8B3wGvAWoB/wHfAa8BagH/AeYBvQGCAf8B9gHlAc0B/wHNAbIB
fgH/AacBfAEpAf8BzgHBAacB/wHRAcgBtAH/AckBuQGaAf8BpgF8ASkB/wHjAc0BpQH/AY0BbwFUAf8D
EQEWOP8BtQF8AQoB/wgAA1cBsgT/AdMB0AHPAf8B0AHPAc0B/wGuAa0BrAH/AwMBBAQAAYQBgQF/Af8B
qAGlAaIB/wGwAa0BqwH/AY8BjQGLAf8MAAMRARYBygHgAfQB/wG1AbABqAH/AeIB4AHfAf8B6gLlAf8B
7wHrAeoB/wHrAeoB6QH/AesB6gHpAf8B6AHnAeYB/wHiAeAB3wX/AVEBlgHPAf8BPgFyAZwB/wwAAxEB
FgT/AbsBoAFtAf8BoAFuAQ4B/wGgAW4BDwH/AaABbgEPAf8BqAF8ASkB/wHLAb4BpAH/Ad8B4AHkAf8B
3QLcAf8B3ALaAf8B3ALaAf8B3ALaAf8B3QHcAd0B/wLeAeEB/wGNAW4BUwH/AxEBFgH2Ad4BugH/AdIB
jwEdAf8B0wGSASQB/wHVAZUBKQH/AdYBlwEvAf8B1wGaATQB/wHZAZ0BOQH/AdoBnwE+Af8B2wGiAUMB
/wHrAcUBhgH/AbIBegEKAf8BwgGGAQ8C/wHiAaoB/wEAAQoB/AH/AbgBgQERAf8MAANWAa4E/wHYAdcB
1QH/AWkCaAHkAxUBHAMDAQQBcQFwAW8B6AG/AbwBugH/AawBqAGmAf8BjwGMAYoB/wMsAUMDLAFDAw0B
EQMQARUB1gHoAfgB/wGVAaoBugH/AbIBrgGoAf8BjAGLAYoB/wK0AbIB/wK1AbMB/wK1AbMB/wKzAbEB
/wGyAa8BqgH/AbUBrwGoAf8BVgGaAdMB/wE/AXMBnQH/DAADEAEVBP8B2QHXAdgB/wHZAdgB2QH/A9kB
/wPZAf8B2QHYAdkB/wHZAtcB/wHZAdcB1gH/AdkB1wHWAf8B2QHXAdYB/wHZAdcB1gH/AdkB1wHWAf8B
2QHXAdYB/wHYAdcB1gH/AY0BbgFTAf8DBAEFAdkBswFqAf8B7QHQAZ4B/wHtAc8BnQH/AewBzwGcAf8B
7AHOAZsB/wHsAc4BmgH/AewBzQGZAf8B7AHNAZgB/wHrAcwBlwH/AewBzAGXAf8B7gHOAZkB/wHtAc4B
mAH/Ae8BzgGRAf8B9wHTAY0B/wFaAVgBVQGzEAADVgGuA/wB/wHzAfIB8wH/AXcBdQFzAf8DPgFqAxMB
GgGvAawBqwH/Ab8BvAG6Af8BqQGmAaQB/wHBAb8BvAH/AuMB4gH/AwYBBwMDAQQBRAF4AaEB/wFBAXYB
oAH/AT8BdQGgAf8BsAGsAagB/wHSAdABzgH/Ac4BzAHKAf8BzAHKAckB/wHQAc0BywH/ATkBcgGhAf8B
QQF2AaAB/wFCAXYBoAH/AVUBWAFaAbQMAAMDAQQBjwFxAVYB/wGOAW8BUwH/AY4BbwFTAf8BjgFvAVMB
/wGOAW8BUwH/AY4BbwFTAf8BjgFvAVMB/wGOAW8BUwH/AY4BbwFTAf8BjgFvAVMB/wGOAW8BUwH/AY4B
bwFTAf8BjgFvAVMB/wGOAW8BUwH/AVkBWAFXAbRUAAMMAQ8BeQF1AXMB9wGzAbIBsQH/AcEBwAG/Af8D
XAHBBAIBcAFvAW4B5QGZAZYBlAH/AYIBgAF+Af8DGwEmGAADWQGzA6cB/wOoAf8DAwEEHAABQgFNAT4H
AAE+AwABKAMAAUADAAEgAwABAQEAAQEGAAEBFgAD/3kAAYALAAGHAfkB/wGBBAABgwHwBgABgQHgBgAB
gAHABgABgAEBBgABwAEDBgAB4AEHBgABwAEPAQABAQQAAYABAQEAAQcHAAEHBwABBwQAAYABhwEAAQcE
AAHAAYcBAAEHBAAB4AIAAQcEAAHwAgABBwIAAv8B+AEBAfgBfws=
</value>
</data>
<metadata name="toolStrip_terminal.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>1409, 17</value>
</metadata>
<metadata name="toolStrip_MasterTraders.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>1012, 17</value>
</metadata>
<metadata name="toolStrip_slaveTraders.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>1264, 17</value>
</metadata>
<metadata name="toolStrip2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>480, 17</value>
</metadata>
<metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>335, 17</value>
</metadata>
<metadata name="toolStrip6.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>2273, 17</value>
</metadata>
<metadata name="toolStrip5.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>2128, 17</value>
</metadata>
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>156</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA
AADGxsb///////j4+P/4+Pj/+/v7/+/v7//Ly8v/w8PD/8jIyP/CwsL/8fHx//v7+//4+Pj/+Pj4////
///FxcX//Pz8//n5+f/5+fn/+fn5//v7+//s7Oz/2NjY/9nZ2f/b29v/2dnZ//Hx8f/6+vr/+fn5//n5
+f/5+fn//Pz8//f39//5+fn/+vr6//r6+v/5+fn//v7+//j4+P/8/Pz//f39//j4+P/+/v7/+vr6//r6
+v/6+vr/+fn5//j4+P/5+fn/+fn5//r6+v/4+Pj//////5+dnv8mISL/VFBR/1dTVP8kHh//mJWW////
///4+Pj/+vr6//r6+v/5+fn/+vr6//r6+v/5+fn//v7+/+vq6v8gGxz/FA4P/xEMDf8QCwz/FQ8Q/xwX
GP/m5ub///////n5+f/6+vr/+vr6//r6+v/6+vr/+vn5//z8/P/39/f/ODM0/wMAAP8cFxj/GxYX/wQA
AP8zLi//8fHx//v7+//5+fn/+vr6//r6+v/6+vr/+vr6//7+/v/r6+v/4uHh/9bV1f9wbW7/KCMk/ykk
Jf9xbm//zs3N///////39/f/+vr6//r6+v/6+vr/+vr6//f39///////fHl6/zMuL//w8PD//////0A7
PP9EQEH/4eDh/+Tj5P/r6uv/8fHx//39/f/6+vr/+vr6//v7+//4+Pj//////5OQkf9eWlv/raur/6Og
of9FQUL/QTw9/zw4Of9VUVH/eXZ3/56cnP//////+Pj4//r6+v/6+vr/+vr6//39/f/u7e3/8PDw/1dT
VP81MDH/RUFC/0hERP+0srL/srCx/yEbHP9jX1////////f39//6+vr/+fn5//r6+v/6+vr/+Pj4////
///S0dH/zs3N/z46O/80MDD//////+7u7v8ZFBX/Y19g///////39/f/+vr6//n5+f/6+vr/+vr6//r6
+v/6+vr/+fj4//////+Fg4P/gX9////////6+vr/cm9v/6Sio///////+Pj4//n5+f/6+vr/+Pj4//n5
+f/6+vr/+fn5//j4+P//////4+Pj/+Pi4//+/v7//v7+/+Tj4//w8PD//f39//n5+f/5+fn/9fX1//v7
+//8/Pz/+vr6//z8/P/8/Pz/+vr6//j4+P/4+Pj/+vr6//r6+v/4+Pj/+Pj4//n5+f/5+fn/+Pj4/+Tk
5P+4uLj/ysrK/8XFxf/Gxsb/xcXF//T09P/7+/v/+fn5//n5+f/6+vr/+fn5//n5+f/5+fn/+fn5//39
/f/CwsL/6enp/+7u7v/09PT/8/Pz//Ly8v/4+Pj/+fn5//n5+f/5+fn/+fn5//n5+f/5+fn/9/f3////
///Dw8P/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAA==
</value>
</data>
</root>
+3 -136
View File
@@ -6,100 +6,21 @@ using System.Linq;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Bson;
using MongoDB.Driver;
using PolyTrader.Core.Persistence;
using PolyTrader.Core.Persistence.Ef;
using PolyTrader.Core.Persistence.Mongo;
using PolyTrader.Modules.CopyTrading.Persistence;
using PolyTrader.Modules.CopyTrading.Persistence.Ef;
using PolyTrader.Modules.CopyTrading.Persistence.Mongo;
using PolyTraderSharp.Models;
namespace PolyTraderSharp.Services
{
/// <summary>
/// Einmalige Config-Migration Mongo → MySQL (Phase 6, Stufe 4).
/// Kopiert NUR Konfiguration: Accounts, aktive Markets, Master-Trader,
/// Copytrading-Account-Settings (inkl. Fallback auf Alt-Felder im accounts-Dokument)
/// und Demo-Positionen (reines App-State). Handels-/Master-Trader-Historie startet leer.
///
/// Aufruf: <c>PolyTraderSharp.exe --migrate-config</c> (kein UI-Start).
/// Liest die Mongo-Verbindung aus Database:ConnectionString/DatabaseName, die
/// MySQL-Verbindung aus Database:MySqlConnectionString (appsettings[.Local].json).
/// Idempotent über Upserts (mehrfacher Aufruf überschreibt, dupliziert nicht).
/// Einmalige Config-Migration nach MySQL (Phase 6, Stufe 4).
/// Quelle: mongoexport-JSON (<see cref="RunFromJson"/>). <see cref="VerifyMySql"/>
/// liest die migrierte Config zur Kontrolle zurück. Idempotent über Upserts.
/// </summary>
public static class ConfigMigrator
{
public static void Run(string mongoConnection, string mongoDbName, string mySqlConnection)
{
Console.WriteLine("=== Config-Migration Mongo -> MySQL ===");
Console.WriteLine($"Quelle (Mongo): {mongoDbName} @ {mongoConnection}");
Console.WriteLine($"Ziel (MySQL): {Redact(mySqlConnection)}");
// --- Quelle: Mongo ---
var mongoDb = new MongoClient(mongoConnection).GetDatabase(mongoDbName);
var srcAccounts = new MongoAccountRepository(mongoDb);
var srcMarkets = new MongoMarketRepository(mongoDb);
var srcPositions = new MongoPositionRepository(mongoDb);
var srcTraders = new MongoTrackedTraderRepository(mongoDb);
var srcSettings = new MongoCopyTradingAccountSettingsRepository(mongoDb);
var rawAccounts = mongoDb.GetCollection<BsonDocument>("accounts");
// --- Ziel: MySQL/EF (eigene Mini-Container für die DbContext-Factories) ---
var target = new ServiceCollection()
.AddDbContextFactory<CoreDbContext>(o => o.UseMySql(mySqlConnection, ServerVersion.AutoDetect(mySqlConnection)))
.AddDbContextFactory<CopyTradingDbContext>(o => o.UseMySql(mySqlConnection, ServerVersion.AutoDetect(mySqlConnection)))
.BuildServiceProvider();
var coreFactory = target.GetRequiredService<IDbContextFactory<CoreDbContext>>();
var ctFactory = target.GetRequiredService<IDbContextFactory<CopyTradingDbContext>>();
IAccountRepository dstAccounts = new EfAccountRepository(coreFactory);
IMarketRepository dstMarkets = new EfMarketRepository(coreFactory);
IPositionRepository dstPositions = new EfPositionRepository(coreFactory);
ITrackedTraderRepository dstTraders = new EfTrackedTraderRepository(ctFactory);
ICopyTradingAccountSettingsRepository dstSettings = new EfCopyTradingAccountSettingsRepository(ctFactory);
dstMarkets.EnsureIndexes();
// 1) Accounts
var accounts = srcAccounts.GetAll();
foreach (var acc in accounts)
dstAccounts.Upsert(acc);
Console.WriteLine($"[OK] Accounts: {accounts.Count}");
// 2) Aktive Markets
var markets = srcMarkets.GetActive();
foreach (var m in markets)
dstMarkets.Upsert(m);
Console.WriteLine($"[OK] Markets (aktiv): {markets.Count}");
// 3) Master-Trader
var traders = srcTraders.GetAll();
foreach (var t in traders)
dstTraders.Upsert(t);
Console.WriteLine($"[OK] Master-Trader: {traders.Count}");
// 4) Copytrading-Account-Settings (mit Fallback auf Alt-Felder)
int settingsCount = MigrateSettings(accounts, srcSettings, dstSettings, rawAccounts);
Console.WriteLine($"[OK] Copytrading-Settings: {settingsCount}");
// 5) Demo-Positionen (reines App-State; Live-Positionen werden aus der Chain rekonstruiert)
int demoPos = 0;
foreach (var acc in accounts.Where(a => a.IsDemo))
{
foreach (var pos in srcPositions.GetDemo(acc.AccountId))
{
dstPositions.UpsertDemo(acc.AccountId, pos);
demoPos++;
}
}
Console.WriteLine($"[OK] Demo-Positionen: {demoPos}");
Console.WriteLine("=== Migration abgeschlossen ===");
}
/// <summary>
/// Config-Migration aus mongoexport-JSON (Ordner mit PolyTraderDB.accounts.json /
/// .trackers.json) → MySQL. Markets bleiben aussen vor (bereits migriert und werden
@@ -321,60 +242,6 @@ namespace PolyTraderSharp.Services
return set;
}
private static int MigrateSettings(
List<AccountState> accounts,
ICopyTradingAccountSettingsRepository src,
ICopyTradingAccountSettingsRepository dst,
IMongoCollection<BsonDocument> rawAccounts)
{
var existing = src.GetAll().ToDictionary(s => s.AccountId);
int count = 0;
foreach (var acc in accounts)
{
if (existing.TryGetValue(acc.AccountId, out var s))
{
dst.Upsert(s);
count++;
continue;
}
var settings = new CopyTradingAccountSettings { AccountId = acc.AccountId };
try
{
var raw = rawAccounts.Find(Builders<BsonDocument>.Filter.Eq("_id", acc.AccountId)).FirstOrDefault();
if (raw != null)
{
settings.PerMarketLimit = Dec(raw, "PerMarketLimit", settings.PerMarketLimit);
settings.MaxPriceDifference = Dec(raw, "MaxPriceDifference", settings.MaxPriceDifference);
settings.MaxBuyPrice = Dec(raw, "MaxBuyPrice", settings.MaxBuyPrice);
settings.ProfitTarget = Dec(raw, "ProfitTarget", settings.ProfitTarget);
settings.PreRedeemLimit = Dec(raw, "PreRedeemLimit", settings.PreRedeemLimit);
settings.PerMasterLimit = Dec(raw, "PerMasterLimit", settings.PerMasterLimit);
settings.perMaxTime6h = Dec(raw, "perMaxTime6h", settings.perMaxTime6h);
settings.perMaxTime24h = Dec(raw, "perMaxTime24h", settings.perMaxTime24h);
settings.perMaxTime72h = Dec(raw, "perMaxTime72h", settings.perMaxTime72h);
settings.perMaxTimeNone = Dec(raw, "perMaxTimeNone", settings.perMaxTimeNone);
}
}
catch { }
dst.Upsert(settings);
count++;
}
return count;
}
private static decimal Dec(BsonDocument doc, string field, decimal fallback)
{
if (doc.TryGetValue(field, out var v))
{
try { return v.ToDecimal(); } catch { }
}
return fallback;
}
private static string Redact(string conn) =>
System.Text.RegularExpressions.Regex.Replace(conn, "(?i)(password=)[^;]*", "$1***");
}
+2 -2
View File
@@ -24,7 +24,7 @@ namespace PolyTraderSharp.Services
_jobStatus = new JobStatusRow
{
JobName = "MongoDB Transaction Log",
JobName = "MySQL Transaction Log",
Description = "Awaits internal signals to write Closed Trades to the database safely.",
StatusText = "Pending Initial Delay..."
};
@@ -97,7 +97,7 @@ namespace PolyTraderSharp.Services
ExitReason = trade.ExitReason
});
_logger.Debug($"Saved ClosedTrade {trade.TradeId} to MongoDB");
_logger.Debug($"Saved ClosedTrade {trade.TradeId} to MySQL");
_jobStatus.LastRun = DateTime.Now;
}
catch (Exception ex)
+1 -1
View File
@@ -23,7 +23,7 @@ namespace PolyTraderSharp.Services
_jobStatus = new JobStatusRow
{
JobName = "LiteDB State Snapshot",
JobName = "State Snapshot",
Description = "Saves active application state (balances, open pos) to snapshot.json.",
StatusText = "Pending Initial Delay..."
};
+5 -40
View File
@@ -3,8 +3,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using MongoDB.Bson;
using MongoDB.Driver;
using PolyTrader.Core.Persistence;
using PolyTrader.Modules.CopyTrading.Persistence;
using PolyTraderSharp.Models;
@@ -26,7 +24,6 @@ namespace PolyTraderSharp.Services
private readonly IPositionRepository _positionRepo;
private readonly ICopyTradingAccountSettingsRepository _accountSettingsRepo;
private readonly ITrackedTraderRepository _traderRepo;
private readonly IMongoDatabase _db;
private readonly TerminalLogger _logger;
public StartupHydrationService(
@@ -36,7 +33,6 @@ namespace PolyTraderSharp.Services
IPositionRepository positionRepo,
ICopyTradingAccountSettingsRepository accountSettingsRepo,
ITrackedTraderRepository traderRepo,
IMongoDatabase db,
TerminalLogger logger)
{
_state = state;
@@ -45,7 +41,6 @@ namespace PolyTraderSharp.Services
_positionRepo = positionRepo;
_accountSettingsRepo = accountSettingsRepo;
_traderRepo = traderRepo;
_db = db;
_logger = logger;
}
@@ -85,14 +80,12 @@ namespace PolyTraderSharp.Services
/// <summary>
/// Lädt die Copytrading-Account-Settings in den State. Existiert für einen Account noch
/// kein Eintrag, werden die Werte einmalig aus den alten AccountState-Feldern des
/// Roh-Dokuments migriert (damit konfigurierte Limits nach dem Modell-Split erhalten bleiben).
/// kein Eintrag, wird ein Default-Eintrag angelegt und persistiert.
/// </summary>
private void HydrateAccountSettings(List<AccountState> accounts)
{
var existing = _accountSettingsRepo.GetAll().ToDictionary(s => s.AccountId);
var rawAccounts = _db.GetCollection<BsonDocument>("accounts");
int migrated = 0;
int created = 0;
foreach (var acc in accounts)
{
@@ -103,41 +96,13 @@ namespace PolyTraderSharp.Services
}
var settings = new CopyTradingAccountSettings { AccountId = acc.AccountId };
try
{
var raw = rawAccounts.Find(Builders<BsonDocument>.Filter.Eq("_id", acc.AccountId)).FirstOrDefault();
if (raw != null)
{
settings.PerMarketLimit = Dec(raw, "PerMarketLimit", settings.PerMarketLimit);
settings.MaxPriceDifference = Dec(raw, "MaxPriceDifference", settings.MaxPriceDifference);
settings.MaxBuyPrice = Dec(raw, "MaxBuyPrice", settings.MaxBuyPrice);
settings.ProfitTarget = Dec(raw, "ProfitTarget", settings.ProfitTarget);
settings.PreRedeemLimit = Dec(raw, "PreRedeemLimit", settings.PreRedeemLimit);
settings.PerMasterLimit = Dec(raw, "PerMasterLimit", settings.PerMasterLimit);
settings.perMaxTime6h = Dec(raw, "perMaxTime6h", settings.perMaxTime6h);
settings.perMaxTime24h = Dec(raw, "perMaxTime24h", settings.perMaxTime24h);
settings.perMaxTime72h = Dec(raw, "perMaxTime72h", settings.perMaxTime72h);
settings.perMaxTimeNone = Dec(raw, "perMaxTimeNone", settings.perMaxTimeNone);
migrated++;
}
}
catch { }
_accountSettingsRepo.Upsert(settings);
_copyState.AccountSettings[acc.AccountId] = settings;
created++;
}
if (migrated > 0)
_logger.Info($"Copytrading-Account-Settings: {migrated} Account(s) aus Alt-Feldern migriert.");
}
private static decimal Dec(BsonDocument doc, string field, decimal fallback)
{
if (doc.TryGetValue(field, out var v))
{
try { return v.ToDecimal(); } catch { }
}
return fallback;
if (created > 0)
_logger.Info($"Copytrading-Account-Settings: {created} Default-Eintrag/-Einträge angelegt.");
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
@@ -8,15 +8,7 @@ namespace PolyTrader.Core.Configuration
{
public const string SectionName = "Database";
/// <summary>"MySql" (Zielzustand) oder "Mongo" (Übergang/Config-Migration).</summary>
public string Provider { get; set; } = "MySql";
/// <summary>MySQL-Connection-String (aus gitignorierter appsettings.Local.json).</summary>
public string MySqlConnectionString { get; set; } = string.Empty;
// --- MongoDB (nur noch für die einmalige Config-Migration nach MySQL) ---
public string ConnectionString { get; set; } = "mongodb://localhost:27017";
public string DatabaseName { get; set; } = "PolyTraderDB";
}
}
@@ -4,37 +4,25 @@ using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Core.Configuration;
using PolyTrader.Core.Persistence;
using PolyTrader.Core.Persistence.Ef;
using PolyTrader.Core.Persistence.Mongo;
namespace PolyTrader.Core.DependencyInjection
{
public static class ServiceCollectionExtensions
{
/// <summary>
/// Registriert die Core-Persistenzschicht hinter den Repository-Interfaces.
/// Provider "MySql" -> EF Core (Pomelo) über einen DbContextFactory (thread-safe,
/// kurzlebiger Context je Operation). Provider "Mongo" -> Mongo-Implementierungen
/// (Übergang / Config-Migration).
/// Registriert die Core-Persistenzschicht (EF Core / Pomelo / MySQL) hinter den
/// Repository-Interfaces über einen DbContextFactory (thread-safe, kurzlebiger
/// Context je Operation).
/// </summary>
public static IServiceCollection AddCorePersistence(this IServiceCollection services, DatabaseOptions options)
{
if (string.Equals(options.Provider, "MySql", StringComparison.OrdinalIgnoreCase))
{
var conn = options.MySqlConnectionString;
services.AddDbContextFactory<CoreDbContext>(o => o.UseMySql(conn, ServerVersion.AutoDetect(conn)));
var conn = options.MySqlConnectionString;
services.AddDbContextFactory<CoreDbContext>(o => o.UseMySql(conn, ServerVersion.AutoDetect(conn)));
services.AddSingleton<IAccountRepository, EfAccountRepository>();
services.AddSingleton<IMarketRepository, EfMarketRepository>();
services.AddSingleton<IPositionRepository, EfPositionRepository>();
services.AddSingleton<ITradeLogRepository, EfTradeLogRepository>();
}
else
{
services.AddSingleton<IAccountRepository, MongoAccountRepository>();
services.AddSingleton<IMarketRepository, MongoMarketRepository>();
services.AddSingleton<IPositionRepository, MongoPositionRepository>();
services.AddSingleton<ITradeLogRepository, MongoTradeLogRepository>();
}
services.AddSingleton<IAccountRepository, EfAccountRepository>();
services.AddSingleton<IMarketRepository, EfMarketRepository>();
services.AddSingleton<IPositionRepository, EfPositionRepository>();
services.AddSingleton<ITradeLogRepository, EfTradeLogRepository>();
return services;
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace PolyTraderSharp.Models
public class AccountState
{
[Browsable(false)]
[MongoDB.Bson.Serialization.Attributes.BsonId] public int AccountId { get; set; }
public int AccountId { get; set; }
[Category("01. General")]
public string Name { get; set; } = string.Empty;
+1 -1
View File
@@ -2,7 +2,7 @@ namespace PolyTraderSharp.Models
{
public class MarketData
{
[MongoDB.Bson.Serialization.Attributes.BsonId] public string Id { get; set; } = string.Empty;
public string Id { get; set; } = string.Empty;
public string ConditionId { get; set; } = string.Empty;
public string Question { get; set; } = string.Empty;
public string Slug { get; set; } = string.Empty;
-1
View File
@@ -7,7 +7,6 @@ namespace PolyTraderSharp.Models
public int AccountId { get; set; }
public bool IsDemo { get; set; }
[MongoDB.Bson.Serialization.Attributes.BsonId]
public string TokenId { get; set; } = string.Empty;
public string MarketSlug { get; set; } = string.Empty;
public string ConditionId { get; set; } = string.Empty;
+1 -2
View File
@@ -11,8 +11,7 @@ namespace PolyTraderSharp.Models
/// </summary>
public class TradeRecord
{
[MongoDB.Bson.Serialization.Attributes.BsonId]
public string Id { get; set; } = MongoDB.Bson.ObjectId.GenerateNewId().ToString();
public string Id { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>Herkunftsmodul, z.B. "CopyTrading".</summary>
public string ModuleName { get; set; } = string.Empty;
@@ -1,24 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver;
using PolyTraderSharp.Models;
namespace PolyTrader.Core.Persistence.Mongo
{
public class MongoAccountRepository : IAccountRepository
{
private readonly IMongoCollection<AccountState> _col;
public MongoAccountRepository(IMongoDatabase db)
{
_col = db.GetCollection<AccountState>("accounts");
}
public List<AccountState> GetAll() => _col.Find(_ => true).ToList();
public void Upsert(AccountState account) =>
_col.ReplaceOne(x => x.AccountId == account.AccountId, account, new ReplaceOptions { IsUpsert = true });
public void Delete(int accountId) => _col.DeleteMany(x => x.AccountId == accountId);
}
}
@@ -1,41 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver;
using PolyTraderSharp.Models;
namespace PolyTrader.Core.Persistence.Mongo
{
public class MongoMarketRepository : IMarketRepository
{
private readonly IMongoCollection<MarketData> _col;
public MongoMarketRepository(IMongoDatabase db)
{
_col = db.GetCollection<MarketData>("markets");
}
public MarketData? GetById(string id) => _col.Find(x => x.Id == id).FirstOrDefault();
public MarketData? FindByTokenId(string tokenId) =>
_col.Find(x => x.ClobTokenIds != null && x.ClobTokenIds.Contains(tokenId)).FirstOrDefault();
public List<MarketData> GetActive() => _col.Find(x => !x.Closed).ToList();
public void Upsert(MarketData market) =>
_col.ReplaceOne(x => x.Id == market.Id, market, new ReplaceOptions { IsUpsert = true });
public void Insert(MarketData market) => _col.InsertOne(market);
public void Update(MarketData market) => _col.ReplaceOne(x => x.Id == market.Id, market);
public void EnsureIndexes()
{
try
{
var keys = Builders<MarketData>.IndexKeys.Ascending(x => x.Id);
_col.Indexes.CreateOne(new CreateIndexModel<MarketData>(keys));
}
catch { }
}
}
}
@@ -1,46 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver;
using PolyTraderSharp.Models;
namespace PolyTrader.Core.Persistence.Mongo
{
public class MongoPositionRepository : IPositionRepository
{
private readonly IMongoDatabase _db;
public MongoPositionRepository(IMongoDatabase db)
{
_db = db;
}
private IMongoCollection<Position> Live(int accountId) =>
_db.GetCollection<Position>($"open_positions_{accountId}");
private IMongoCollection<Position> Demo(int accountId) =>
_db.GetCollection<Position>($"demo_positions_{accountId}");
public List<Position> GetLive(int accountId) => Live(accountId).Find(_ => true).ToList();
public List<Position> GetDemo(int accountId) => Demo(accountId).Find(_ => true).ToList();
public Position? FindLive(int accountId, string tokenId) =>
Live(accountId).Find(x => x.TokenId == tokenId).FirstOrDefault();
public Position? FindDemo(int accountId, string tokenId) =>
Demo(accountId).Find(x => x.TokenId == tokenId).FirstOrDefault();
public void UpsertLive(int accountId, Position position) =>
Live(accountId).ReplaceOne(x => x.TokenId == position.TokenId, position, new ReplaceOptions { IsUpsert = true });
public void UpsertDemo(int accountId, Position position) =>
Demo(accountId).ReplaceOne(x => x.TokenId == position.TokenId, position, new ReplaceOptions { IsUpsert = true });
public void DeleteLive(int accountId, string tokenId) =>
Live(accountId).DeleteOne(x => x.TokenId == tokenId);
public void DeleteDemo(int accountId, string tokenId) =>
Demo(accountId).DeleteOne(x => x.TokenId == tokenId);
public void DropDemo(int accountId) => _db.DropCollection($"demo_positions_{accountId}");
}
}
@@ -1,41 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using MongoDB.Driver;
using PolyTraderSharp.Models;
namespace PolyTrader.Core.Persistence.Mongo
{
public class MongoTradeLogRepository : ITradeLogRepository
{
private readonly IMongoCollection<TradeRecord> _col;
public MongoTradeLogRepository(IMongoDatabase db)
{
_col = db.GetCollection<TradeRecord>("trade_log");
}
public void EnsureIndexes()
{
try
{
_col.Indexes.CreateOne(new CreateIndexModel<TradeRecord>(
Builders<TradeRecord>.IndexKeys.Descending(x => x.ClosedAt)));
_col.Indexes.CreateOne(new CreateIndexModel<TradeRecord>(
Builders<TradeRecord>.IndexKeys.Ascending(x => x.ModuleName)));
_col.Indexes.CreateOne(new CreateIndexModel<TradeRecord>(
Builders<TradeRecord>.IndexKeys.Ascending(x => x.AccountId)));
}
catch { }
}
public void Insert(TradeRecord record) => _col.InsertOne(record);
public List<TradeRecord> GetRecent(int limit) =>
_col.Find(_ => true).SortByDescending(x => x.ClosedAt).Limit(limit).ToList();
public List<TradeRecord> Find(Expression<Func<TradeRecord, bool>> predicate) =>
_col.Find(predicate).ToList();
}
}
@@ -10,7 +10,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.24.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
<PrivateAssets>all</PrivateAssets>
@@ -2,7 +2,6 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using PolyTrader.Core.Persistence;
using PolyTraderSharp.Models;
using PolyTraderSharp.Services;
@@ -8,7 +8,6 @@ using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Core.Modularity;
using PolyTrader.Modules.CopyTrading.Persistence;
using PolyTrader.Modules.CopyTrading.Persistence.Ef;
using PolyTrader.Modules.CopyTrading.Persistence.Mongo;
using PolyTraderSharp;
using PolyTraderSharp.Models;
using PolyTraderSharp.Services;
@@ -37,25 +36,14 @@ namespace PolyTrader.Modules.CopyTrading
services.AddSingleton(closedTradeChannel.Writer);
services.AddSingleton(closedTradeChannel.Reader);
// Modul-Persistenz: Provider-Toggle (MySql via EF / Mongo im Übergang).
var provider = configuration["Database:Provider"] ?? "Mongo";
if (string.Equals(provider, "MySql", StringComparison.OrdinalIgnoreCase))
{
var conn = configuration["Database:MySqlConnectionString"] ?? string.Empty;
services.AddDbContextFactory<CopyTradingDbContext>(o => o.UseMySql(conn, ServerVersion.AutoDetect(conn)));
// Modul-Persistenz: EF Core / Pomelo / MySQL (thread-safer DbContextFactory).
var conn = configuration["Database:MySqlConnectionString"] ?? string.Empty;
services.AddDbContextFactory<CopyTradingDbContext>(o => o.UseMySql(conn, ServerVersion.AutoDetect(conn)));
services.AddSingleton<ICopyTradeLogRepository, EfCopyTradeLogRepository>();
services.AddSingleton<ICopyTradingAccountSettingsRepository, EfCopyTradingAccountSettingsRepository>();
services.AddSingleton<ITrackedTraderRepository, EfTrackedTraderRepository>();
services.AddSingleton<IMasterTraderHistoryRepository, EfMasterTraderHistoryRepository>();
}
else
{
services.AddSingleton<ICopyTradeLogRepository, MongoCopyTradeLogRepository>();
services.AddSingleton<ICopyTradingAccountSettingsRepository, MongoCopyTradingAccountSettingsRepository>();
services.AddSingleton<ITrackedTraderRepository, MongoTrackedTraderRepository>();
services.AddSingleton<IMasterTraderHistoryRepository, MongoMasterTraderHistoryRepository>();
}
services.AddSingleton<ICopyTradeLogRepository, EfCopyTradeLogRepository>();
services.AddSingleton<ICopyTradingAccountSettingsRepository, EfCopyTradingAccountSettingsRepository>();
services.AddSingleton<ITrackedTraderRepository, EfTrackedTraderRepository>();
services.AddSingleton<IMasterTraderHistoryRepository, EfMasterTraderHistoryRepository>();
// Modul-Services (Signalquelle, Ausführung, Analytics)
services.AddSingleton<TraderMonitorService>();
@@ -2,7 +2,7 @@ namespace PolyTraderSharp.Models
{
public class ClosedTrade
{
[MongoDB.Bson.Serialization.Attributes.BsonId] public int TradeId { get; set; }
public int TradeId { get; set; }
public int AccountId { get; set; }
public int SourceTraderId { get; set; }
public bool IsDemo { get; set; }
@@ -6,12 +6,11 @@ namespace PolyTraderSharp.Models
/// Copytrading-spezifische Detail-Einstellungen je Account (Investment-/Zeit-Limits).
/// Bewusst getrennt vom Core-<see cref="AccountState"/> (allgemeine Account-Daten):
/// Diese Werte gehören dem Copytrading-Modul und werden im Modul-View bearbeitet.
/// Persistiert in der Collection "ct_account_settings" (BsonId = AccountId).
/// Persistiert in Tabelle "mod_copytrading_account_settings" (Key = AccountId).
/// </summary>
public class CopyTradingAccountSettings
{
[Browsable(false)]
[MongoDB.Bson.Serialization.Attributes.BsonId]
public int AccountId { get; set; }
[Category("01. Risk Management")]
@@ -4,7 +4,7 @@ namespace PolyTraderSharp.Models
{
public class MasterTraderHistoryRecord
{
[MongoDB.Bson.Serialization.Attributes.BsonId] public string Id { get; set; } = MongoDB.Bson.ObjectId.GenerateNewId().ToString();
public string Id { get; set; } = Guid.NewGuid().ToString("N");
// Verknüpfung zum Master Trader
public int TraderId { get; set; }
@@ -7,7 +7,7 @@ namespace PolyTraderSharp.Models
public class TrackedTrader
{
[Browsable(false)]
[MongoDB.Bson.Serialization.Attributes.BsonId] public int Id { get; set; }
public int Id { get; set; }
[Category("01. Identification")]
public string WalletAddress { get; set; } = string.Empty;
@@ -1,39 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver;
using PolyTraderSharp.Models;
namespace PolyTrader.Modules.CopyTrading.Persistence.Mongo
{
public class MongoMasterTraderHistoryRepository : IMasterTraderHistoryRepository
{
private readonly IMongoCollection<MasterTraderHistoryRecord> _col;
public MongoMasterTraderHistoryRepository(IMongoDatabase db)
{
_col = db.GetCollection<MasterTraderHistoryRecord>("mt_history");
}
public void EnsureIndexes()
{
try
{
_col.Indexes.CreateOne(new CreateIndexModel<MasterTraderHistoryRecord>(
Builders<MasterTraderHistoryRecord>.IndexKeys.Ascending(x => x.TraderId)));
_col.Indexes.CreateOne(new CreateIndexModel<MasterTraderHistoryRecord>(
Builders<MasterTraderHistoryRecord>.IndexKeys.Ascending(x => x.ClosedAt)));
}
catch { }
}
public bool Exists(int traderId, string tokenId, DateTime windowStart, DateTime windowEnd) =>
_col.Find(x => x.TraderId == traderId && x.TokenId == tokenId
&& x.ClosedAt >= windowStart && x.ClosedAt <= windowEnd).Any();
public void Insert(MasterTraderHistoryRecord record) => _col.InsertOne(record);
public List<MasterTraderHistoryRecord> GetByTraderSince(int traderId, DateTime since) =>
_col.Find(x => x.TraderId == traderId && x.ClosedAt >= since).ToList();
}
}
@@ -1,26 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver;
using PolyTraderSharp.Models;
namespace PolyTrader.Modules.CopyTrading.Persistence.Mongo
{
public class MongoTrackedTraderRepository : ITrackedTraderRepository
{
private readonly IMongoCollection<TrackedTrader> _col;
public MongoTrackedTraderRepository(IMongoDatabase db)
{
_col = db.GetCollection<TrackedTrader>("trackers");
}
public List<TrackedTrader> GetAll() => _col.Find(_ => true).ToList();
public void Upsert(TrackedTrader trader) =>
_col.ReplaceOne(x => x.Id == trader.Id, trader, new ReplaceOptions { IsUpsert = true });
public void Update(TrackedTrader trader) => _col.ReplaceOne(x => x.Id == trader.Id, trader);
public void Delete(int id) => _col.DeleteOne(x => x.Id == id);
}
}
@@ -1,45 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using MongoDB.Driver;
using PolyTraderSharp.Models;
namespace PolyTrader.Modules.CopyTrading.Persistence
{
public class MongoCopyTradeLogRepository : ICopyTradeLogRepository
{
private readonly IMongoCollection<ClosedTrade> _col;
public MongoCopyTradeLogRepository(IMongoDatabase db)
{
_col = db.GetCollection<ClosedTrade>("closed_trades");
}
public void EnsureIndexes()
{
CreateIndex(x => x.TradeId);
CreateIndex(x => x.AccountId);
CreateIndex(x => x.TokenId);
CreateIndex(x => x.SourceTraderId);
}
private void CreateIndex(Expression<Func<ClosedTrade, object>> field)
{
try
{
var keys = Builders<ClosedTrade>.IndexKeys.Ascending(field);
_col.Indexes.CreateOne(new CreateIndexModel<ClosedTrade>(keys));
}
catch { }
}
public bool Exists(int accountId, string tokenId) =>
_col.Find(x => x.AccountId == accountId && x.TokenId == tokenId).Any();
public void Insert(ClosedTrade trade) => _col.InsertOne(trade);
public List<ClosedTrade> Find(Expression<Func<ClosedTrade, bool>> predicate) =>
_col.Find(predicate).ToList();
}
}
@@ -1,27 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver;
using PolyTraderSharp.Models;
namespace PolyTrader.Modules.CopyTrading.Persistence
{
public class MongoCopyTradingAccountSettingsRepository : ICopyTradingAccountSettingsRepository
{
private readonly IMongoCollection<CopyTradingAccountSettings> _col;
public MongoCopyTradingAccountSettingsRepository(IMongoDatabase db)
{
_col = db.GetCollection<CopyTradingAccountSettings>("ct_account_settings");
}
public List<CopyTradingAccountSettings> GetAll() => _col.Find(_ => true).ToList();
public CopyTradingAccountSettings? Get(int accountId) =>
_col.Find(x => x.AccountId == accountId).FirstOrDefault();
public void Upsert(CopyTradingAccountSettings settings) =>
_col.ReplaceOne(x => x.AccountId == settings.AccountId, settings, new ReplaceOptions { IsUpsert = true });
public void Delete(int accountId) => _col.DeleteOne(x => x.AccountId == accountId);
}
}
@@ -52,7 +52,7 @@ namespace PolyTraderSharp.Services
public override async Task StartAsync(CancellationToken cancellationToken)
{
_logger.Info("Starte Preload des MarketCache aus MongoDB um Flaschenhälse zu vermeiden...");
_logger.Info("Starte Preload des MarketCache aus der Datenbank um Flaschenhälse zu vermeiden...");
{
// Initialize cache for EVERYTHING in DB that is not closed!
var activeMarkets = _marketRepo.GetActive();