Phase 6 (Stufe 3): CopyTrading-Modul auf EF/MySQL umschaltbar
- CopyTradingDbContext (mod_copytrading_* Tabellen) + Design-Time-Factory - EF-Repos: CopyTradeLog, AccountSettings, TrackedTrader, MasterTraderHistory - Neue Repo-Contracts ITrackedTraderRepository + IMasterTraderHistoryRepository (loest die Collection-Inkonsistenz trackers/tracked_traders auf eine Quelle auf) - Mongo-Impls der neuen Contracts (Uebergang) - CopyTradingModule.RegisterServices: Provider-Toggle (MySql via EF / Mongo) - StartupHydrationService + MasterTraderAnalyticsJob nutzen die Repos statt _db - InitialCopyTrading-Migration erstellt und auf MySQL angewendet Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d8cfdbf6be
commit
8101b79cfb
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using MongoDB.Driver;
|
using PolyTrader.Modules.CopyTrading.Persistence;
|
||||||
using PolyTraderSharp.Extensions;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -16,16 +15,18 @@ namespace PolyTraderSharp.Services
|
|||||||
private readonly TradingState _state;
|
private readonly TradingState _state;
|
||||||
private readonly CopyTradingState _copyState;
|
private readonly CopyTradingState _copyState;
|
||||||
private readonly TerminalLogger _logger;
|
private readonly TerminalLogger _logger;
|
||||||
private readonly IMongoDatabase _db;
|
private readonly IMasterTraderHistoryRepository _historyRepo;
|
||||||
|
private readonly ITrackedTraderRepository _traderRepo;
|
||||||
private readonly JobStatusRow _jobStatus;
|
private readonly JobStatusRow _jobStatus;
|
||||||
private readonly PolymarketApiService _api;
|
private readonly PolymarketApiService _api;
|
||||||
|
|
||||||
public MasterTraderAnalyticsJob(TradingState state, CopyTradingState copyState, TerminalLogger logger, IMongoDatabase db, JobManager jobManager, PolymarketApiService api)
|
public MasterTraderAnalyticsJob(TradingState state, CopyTradingState copyState, TerminalLogger logger, IMasterTraderHistoryRepository historyRepo, ITrackedTraderRepository traderRepo, JobManager jobManager, PolymarketApiService api)
|
||||||
{
|
{
|
||||||
_state = state;
|
_state = state;
|
||||||
_copyState = copyState;
|
_copyState = copyState;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_db = db;
|
_historyRepo = historyRepo;
|
||||||
|
_traderRepo = traderRepo;
|
||||||
_api = api;
|
_api = api;
|
||||||
|
|
||||||
_jobStatus = new JobStatusRow
|
_jobStatus = new JobStatusRow
|
||||||
@@ -87,9 +88,7 @@ namespace PolyTraderSharp.Services
|
|||||||
{
|
{
|
||||||
_logger.Info("🔄 Starte Master-Trader Historien-Download und Performance-Analyse...");
|
_logger.Info("🔄 Starte Master-Trader Historien-Download und Performance-Analyse...");
|
||||||
|
|
||||||
var historyColl = _db.GetCollection<MasterTraderHistoryRecord>("mt_history");
|
_historyRepo.EnsureIndexes();
|
||||||
historyColl.EnsureIndex(x => x.TraderId);
|
|
||||||
historyColl.EnsureIndex(x => x.ClosedAt);
|
|
||||||
|
|
||||||
DateTime cutoff7Days = DateTime.UtcNow.AddDays(-7);
|
DateTime cutoff7Days = DateTime.UtcNow.AddDays(-7);
|
||||||
var tradersToAnalyze = _copyState.Traders.Values.Where(t => t.IsActive && !string.IsNullOrEmpty(t.WalletAddress)).ToList();
|
var tradersToAnalyze = _copyState.Traders.Values.Where(t => t.IsActive && !string.IsNullOrEmpty(t.WalletAddress)).ToList();
|
||||||
@@ -145,7 +144,7 @@ namespace PolyTraderSharp.Services
|
|||||||
DateTime windowStart = closedTs.AddSeconds(-2);
|
DateTime windowStart = closedTs.AddSeconds(-2);
|
||||||
DateTime windowEnd = closedTs.AddSeconds(2);
|
DateTime windowEnd = closedTs.AddSeconds(2);
|
||||||
|
|
||||||
bool exists = historyColl.LiteFindOne(x => x.TraderId == trader.Id && x.TokenId == tokenId && x.ClosedAt >= windowStart && x.ClosedAt <= windowEnd) != null;
|
bool exists = _historyRepo.Exists(trader.Id, tokenId, windowStart, windowEnd);
|
||||||
if (!exists)
|
if (!exists)
|
||||||
{
|
{
|
||||||
var record = new MasterTraderHistoryRecord
|
var record = new MasterTraderHistoryRecord
|
||||||
@@ -155,7 +154,7 @@ namespace PolyTraderSharp.Services
|
|||||||
ClosedAt = closedTs,
|
ClosedAt = closedTs,
|
||||||
RealizedPnl = pnl
|
RealizedPnl = pnl
|
||||||
};
|
};
|
||||||
historyColl.Insert(record);
|
_historyRepo.Insert(record);
|
||||||
inserted++;
|
inserted++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +163,7 @@ namespace PolyTraderSharp.Services
|
|||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
|
|
||||||
// 2. Calculate Stats from DB
|
// 2. Calculate Stats from DB
|
||||||
var last7DaysTrades = historyColl.LiteFind(x => x.TraderId == trader.Id && x.ClosedAt >= cutoff7Days).ToList();
|
var last7DaysTrades = _historyRepo.GetByTraderSince(trader.Id, cutoff7Days);
|
||||||
|
|
||||||
trader.TotalTrades = last7DaysTrades.Count;
|
trader.TotalTrades = last7DaysTrades.Count;
|
||||||
trader.TotalPnl = (double)last7DaysTrades.Sum(x => x.RealizedPnl);
|
trader.TotalPnl = (double)last7DaysTrades.Sum(x => x.RealizedPnl);
|
||||||
@@ -174,8 +173,7 @@ namespace PolyTraderSharp.Services
|
|||||||
trader.Winrate30t = trader.TotalTrades > 0 ? Math.Round(((double)trader.WinningTrades / trader.TotalTrades) * 100, 2) : 0;
|
trader.Winrate30t = trader.TotalTrades > 0 ? Math.Round(((double)trader.WinningTrades / trader.TotalTrades) * 100, 2) : 0;
|
||||||
|
|
||||||
// Save updated trader to DB so UI updates
|
// Save updated trader to DB so UI updates
|
||||||
var tColl = _db.GetCollection<TrackedTrader>("tracked_traders");
|
_traderRepo.Update(trader);
|
||||||
tColl.Update(trader);
|
|
||||||
|
|
||||||
if (inserted > 0 && trader.TotalTrades > 0)
|
if (inserted > 0 && trader.TotalTrades > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace PolyTraderSharp.Services
|
|||||||
private readonly IAccountRepository _accountRepo;
|
private readonly IAccountRepository _accountRepo;
|
||||||
private readonly IPositionRepository _positionRepo;
|
private readonly IPositionRepository _positionRepo;
|
||||||
private readonly ICopyTradingAccountSettingsRepository _accountSettingsRepo;
|
private readonly ICopyTradingAccountSettingsRepository _accountSettingsRepo;
|
||||||
|
private readonly ITrackedTraderRepository _traderRepo;
|
||||||
private readonly IMongoDatabase _db;
|
private readonly IMongoDatabase _db;
|
||||||
private readonly TerminalLogger _logger;
|
private readonly TerminalLogger _logger;
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ namespace PolyTraderSharp.Services
|
|||||||
IAccountRepository accountRepo,
|
IAccountRepository accountRepo,
|
||||||
IPositionRepository positionRepo,
|
IPositionRepository positionRepo,
|
||||||
ICopyTradingAccountSettingsRepository accountSettingsRepo,
|
ICopyTradingAccountSettingsRepository accountSettingsRepo,
|
||||||
|
ITrackedTraderRepository traderRepo,
|
||||||
IMongoDatabase db,
|
IMongoDatabase db,
|
||||||
TerminalLogger logger)
|
TerminalLogger logger)
|
||||||
{
|
{
|
||||||
@@ -42,6 +44,7 @@ namespace PolyTraderSharp.Services
|
|||||||
_accountRepo = accountRepo;
|
_accountRepo = accountRepo;
|
||||||
_positionRepo = positionRepo;
|
_positionRepo = positionRepo;
|
||||||
_accountSettingsRepo = accountSettingsRepo;
|
_accountSettingsRepo = accountSettingsRepo;
|
||||||
|
_traderRepo = traderRepo;
|
||||||
_db = db;
|
_db = db;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
@@ -65,10 +68,7 @@ namespace PolyTraderSharp.Services
|
|||||||
|
|
||||||
HydrateAccountSettings(accounts);
|
HydrateAccountSettings(accounts);
|
||||||
|
|
||||||
// Trader (TrackedTrader) sind aktuell noch modulnah; bis zum Trader-Repo in
|
foreach (var trd in _traderRepo.GetAll())
|
||||||
// Phase 5 direkt aus der "trackers"-Collection geladen.
|
|
||||||
var tradersCol = _db.GetCollection<TrackedTrader>("trackers");
|
|
||||||
foreach (var trd in tradersCol.Find(_ => true).ToList())
|
|
||||||
{
|
{
|
||||||
_copyState.Traders[trd.Id] = trd;
|
_copyState.Traders[trd.Id] = trd;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using PolyTrader.Core.Modularity;
|
using PolyTrader.Core.Modularity;
|
||||||
using PolyTrader.Modules.CopyTrading.Persistence;
|
using PolyTrader.Modules.CopyTrading.Persistence;
|
||||||
|
using PolyTrader.Modules.CopyTrading.Persistence.Ef;
|
||||||
|
using PolyTrader.Modules.CopyTrading.Persistence.Mongo;
|
||||||
using PolyTraderSharp;
|
using PolyTraderSharp;
|
||||||
using PolyTraderSharp.Models;
|
using PolyTraderSharp.Models;
|
||||||
using PolyTraderSharp.Services;
|
using PolyTraderSharp.Services;
|
||||||
@@ -33,11 +37,25 @@ namespace PolyTrader.Modules.CopyTrading
|
|||||||
services.AddSingleton(closedTradeChannel.Writer);
|
services.AddSingleton(closedTradeChannel.Writer);
|
||||||
services.AddSingleton(closedTradeChannel.Reader);
|
services.AddSingleton(closedTradeChannel.Reader);
|
||||||
|
|
||||||
// Modul-eigener Trade-Log
|
// Modul-Persistenz: Provider-Toggle (MySql via EF / Mongo im Übergang).
|
||||||
services.AddSingleton<ICopyTradeLogRepository, MongoCopyTradeLogRepository>();
|
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)));
|
||||||
|
|
||||||
// Copytrading-Account-Detail-Einstellungen
|
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<ICopyTradingAccountSettingsRepository, MongoCopyTradingAccountSettingsRepository>();
|
||||||
|
services.AddSingleton<ITrackedTraderRepository, MongoTrackedTraderRepository>();
|
||||||
|
services.AddSingleton<IMasterTraderHistoryRepository, MongoMasterTraderHistoryRepository>();
|
||||||
|
}
|
||||||
|
|
||||||
// Modul-Services (Signalquelle, Ausführung, Analytics)
|
// Modul-Services (Signalquelle, Ausführung, Analytics)
|
||||||
services.AddSingleton<TraderMonitorService>();
|
services.AddSingleton<TraderMonitorService>();
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||||
|
using PolyTraderSharp.Models;
|
||||||
|
|
||||||
|
namespace PolyTrader.Modules.CopyTrading.Persistence.Ef
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// EF-Core-Kontext für die Entitäten des Copytrading-Moduls (gleiche MySQL-DB wie der
|
||||||
|
/// Core, eigene Tabellen mit Präfix mod_copytrading_).
|
||||||
|
/// </summary>
|
||||||
|
public class CopyTradingDbContext : DbContext
|
||||||
|
{
|
||||||
|
public CopyTradingDbContext(DbContextOptions<CopyTradingDbContext> options) : base(options) { }
|
||||||
|
|
||||||
|
public DbSet<ClosedTrade> ClosedTrades => Set<ClosedTrade>();
|
||||||
|
public DbSet<TrackedTrader> Traders => Set<TrackedTrader>();
|
||||||
|
public DbSet<CopyTradingAccountSettings> AccountSettings => Set<CopyTradingAccountSettings>();
|
||||||
|
public DbSet<MasterTraderHistoryRecord> History => Set<MasterTraderHistoryRecord>();
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder b)
|
||||||
|
{
|
||||||
|
b.Ignore<ClosedTradeRow>(); // reine UI-Anzeige-Klasse
|
||||||
|
|
||||||
|
b.Entity<ClosedTrade>(e =>
|
||||||
|
{
|
||||||
|
e.ToTable("mod_copytrading_closed_trades");
|
||||||
|
e.HasKey(x => x.TradeId);
|
||||||
|
e.Property(x => x.TradeId).ValueGeneratedNever();
|
||||||
|
e.Property(x => x.TokenId).HasMaxLength(120);
|
||||||
|
e.Property(x => x.MarketSlug).HasMaxLength(300);
|
||||||
|
e.Property(x => x.MarketQuestion).HasMaxLength(1000);
|
||||||
|
e.Property(x => x.Outcome).HasMaxLength(200);
|
||||||
|
e.Property(x => x.Side).HasMaxLength(10);
|
||||||
|
e.Property(x => x.ExitReason).HasMaxLength(200);
|
||||||
|
e.Property(x => x.EntryPrice).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.ExitPrice).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.Size).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.RealizedPnl).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.PnlPercent).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.TotalFees).HasPrecision(18, 6);
|
||||||
|
e.HasIndex(x => x.AccountId);
|
||||||
|
e.HasIndex(x => x.TokenId);
|
||||||
|
e.HasIndex(x => x.SourceTraderId);
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Entity<TrackedTrader>(e =>
|
||||||
|
{
|
||||||
|
e.ToTable("mod_copytrading_traders");
|
||||||
|
e.HasKey(x => x.Id);
|
||||||
|
e.Property(x => x.Id).ValueGeneratedNever();
|
||||||
|
e.Property(x => x.WalletAddress).HasMaxLength(128);
|
||||||
|
e.Property(x => x.DisplayName).HasMaxLength(200);
|
||||||
|
e.Property(x => x.Category).HasMaxLength(64);
|
||||||
|
e.Property(x => x.Description).HasMaxLength(1000);
|
||||||
|
e.Property(x => x.Reasoning).HasMaxLength(1000);
|
||||||
|
|
||||||
|
var comparer = new ValueComparer<HashSet<int>>(
|
||||||
|
(a, c) => (a == null && c == null) || (a != null && c != null && a.SetEquals(c)),
|
||||||
|
v => v.Aggregate(0, (h, i) => HashCode.Combine(h, i)),
|
||||||
|
v => new HashSet<int>(v));
|
||||||
|
|
||||||
|
e.Property(x => x.AssignedAccountIds)
|
||||||
|
.HasConversion(
|
||||||
|
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
|
||||||
|
v => string.IsNullOrEmpty(v) ? new HashSet<int>() : (JsonSerializer.Deserialize<HashSet<int>>(v, (JsonSerializerOptions?)null) ?? new HashSet<int>()))
|
||||||
|
.HasColumnType("text");
|
||||||
|
e.Property(x => x.AssignedAccountIds).Metadata.SetValueComparer(comparer);
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Entity<CopyTradingAccountSettings>(e =>
|
||||||
|
{
|
||||||
|
e.ToTable("mod_copytrading_account_settings");
|
||||||
|
e.HasKey(x => x.AccountId);
|
||||||
|
e.Property(x => x.AccountId).ValueGeneratedNever();
|
||||||
|
e.Property(x => x.PerMarketLimit).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.MaxPriceDifference).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.MaxBuyPrice).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.ProfitTarget).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.PreRedeemLimit).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.PerMasterLimit).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.perMaxTime6h).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.perMaxTime24h).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.perMaxTime72h).HasPrecision(18, 6);
|
||||||
|
e.Property(x => x.perMaxTimeNone).HasPrecision(18, 6);
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Entity<MasterTraderHistoryRecord>(e =>
|
||||||
|
{
|
||||||
|
e.ToTable("mod_copytrading_mt_history");
|
||||||
|
e.HasKey(x => x.Id);
|
||||||
|
e.Property(x => x.Id).HasMaxLength(64);
|
||||||
|
e.Property(x => x.TokenId).HasMaxLength(120);
|
||||||
|
e.Property(x => x.RealizedPnl).HasPrecision(18, 6);
|
||||||
|
e.HasIndex(x => x.TraderId);
|
||||||
|
e.HasIndex(x => x.ClosedAt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
|
|
||||||
|
namespace PolyTrader.Modules.CopyTrading.Persistence.Ef
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Design-Time-Factory für EF-Tooling. Connection über Umgebungsvariable POLYTRADER_MYSQL
|
||||||
|
/// (keine Zugangsdaten im Code/Repo).
|
||||||
|
/// </summary>
|
||||||
|
public class CopyTradingDbContextFactory : IDesignTimeDbContextFactory<CopyTradingDbContext>
|
||||||
|
{
|
||||||
|
public CopyTradingDbContext CreateDbContext(string[] args)
|
||||||
|
{
|
||||||
|
var conn = Environment.GetEnvironmentVariable("POLYTRADER_MYSQL")
|
||||||
|
?? "Server=localhost;Port=3306;Database=polytrader;User ID=root;Password=;";
|
||||||
|
|
||||||
|
var options = new DbContextOptionsBuilder<CopyTradingDbContext>()
|
||||||
|
.UseMySql(conn, ServerVersion.AutoDetect(conn))
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
return new CopyTradingDbContext(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using PolyTraderSharp.Models;
|
||||||
|
|
||||||
|
namespace PolyTrader.Modules.CopyTrading.Persistence.Ef
|
||||||
|
{
|
||||||
|
public class EfCopyTradeLogRepository : ICopyTradeLogRepository
|
||||||
|
{
|
||||||
|
private readonly IDbContextFactory<CopyTradingDbContext> _factory;
|
||||||
|
|
||||||
|
public EfCopyTradeLogRepository(IDbContextFactory<CopyTradingDbContext> factory) => _factory = factory;
|
||||||
|
|
||||||
|
public void EnsureIndexes() { }
|
||||||
|
|
||||||
|
public bool Exists(int accountId, string tokenId)
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
return ctx.ClosedTrades.AsNoTracking().Any(x => x.AccountId == accountId && x.TokenId == tokenId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(ClosedTrade trade)
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
ctx.ClosedTrades.Add(trade);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ClosedTrade> Find(Expression<Func<ClosedTrade, bool>> predicate)
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
return ctx.ClosedTrades.AsNoTracking().Where(predicate).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using PolyTraderSharp.Models;
|
||||||
|
|
||||||
|
namespace PolyTrader.Modules.CopyTrading.Persistence.Ef
|
||||||
|
{
|
||||||
|
public class EfCopyTradingAccountSettingsRepository : ICopyTradingAccountSettingsRepository
|
||||||
|
{
|
||||||
|
private readonly IDbContextFactory<CopyTradingDbContext> _factory;
|
||||||
|
|
||||||
|
public EfCopyTradingAccountSettingsRepository(IDbContextFactory<CopyTradingDbContext> factory) => _factory = factory;
|
||||||
|
|
||||||
|
public List<CopyTradingAccountSettings> GetAll()
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
return ctx.AccountSettings.AsNoTracking().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CopyTradingAccountSettings? Get(int accountId)
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
return ctx.AccountSettings.AsNoTracking().FirstOrDefault(x => x.AccountId == accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Upsert(CopyTradingAccountSettings settings)
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
var existing = ctx.AccountSettings.Find(settings.AccountId);
|
||||||
|
if (existing == null)
|
||||||
|
ctx.AccountSettings.Add(settings);
|
||||||
|
else
|
||||||
|
ctx.Entry(existing).CurrentValues.SetValues(settings);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(int accountId)
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
var existing = ctx.AccountSettings.Find(accountId);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
ctx.AccountSettings.Remove(existing);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using PolyTraderSharp.Models;
|
||||||
|
|
||||||
|
namespace PolyTrader.Modules.CopyTrading.Persistence.Ef
|
||||||
|
{
|
||||||
|
public class EfMasterTraderHistoryRepository : IMasterTraderHistoryRepository
|
||||||
|
{
|
||||||
|
private readonly IDbContextFactory<CopyTradingDbContext> _factory;
|
||||||
|
|
||||||
|
public EfMasterTraderHistoryRepository(IDbContextFactory<CopyTradingDbContext> factory) => _factory = factory;
|
||||||
|
|
||||||
|
public void EnsureIndexes() { }
|
||||||
|
|
||||||
|
public bool Exists(int traderId, string tokenId, DateTime windowStart, DateTime windowEnd)
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
return ctx.History.AsNoTracking().Any(x => x.TraderId == traderId && x.TokenId == tokenId
|
||||||
|
&& x.ClosedAt >= windowStart && x.ClosedAt <= windowEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(MasterTraderHistoryRecord record)
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
ctx.History.Add(record);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MasterTraderHistoryRecord> GetByTraderSince(int traderId, DateTime since)
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
return ctx.History.AsNoTracking().Where(x => x.TraderId == traderId && x.ClosedAt >= since).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using PolyTraderSharp.Models;
|
||||||
|
|
||||||
|
namespace PolyTrader.Modules.CopyTrading.Persistence.Ef
|
||||||
|
{
|
||||||
|
public class EfTrackedTraderRepository : ITrackedTraderRepository
|
||||||
|
{
|
||||||
|
private readonly IDbContextFactory<CopyTradingDbContext> _factory;
|
||||||
|
|
||||||
|
public EfTrackedTraderRepository(IDbContextFactory<CopyTradingDbContext> factory) => _factory = factory;
|
||||||
|
|
||||||
|
public List<TrackedTrader> GetAll()
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
return ctx.Traders.AsNoTracking().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Upsert(TrackedTrader trader)
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
var existing = ctx.Traders.Find(trader.Id);
|
||||||
|
if (existing == null)
|
||||||
|
ctx.Traders.Add(trader);
|
||||||
|
else
|
||||||
|
ctx.Entry(existing).CurrentValues.SetValues(trader);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(TrackedTrader trader) => Upsert(trader);
|
||||||
|
|
||||||
|
public void Delete(int id)
|
||||||
|
{
|
||||||
|
using var ctx = _factory.CreateDbContext();
|
||||||
|
var existing = ctx.Traders.Find(id);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
ctx.Traders.Remove(existing);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+252
@@ -0,0 +1,252 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using PolyTrader.Modules.CopyTrading.Persistence.Ef;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PolyTrader.Modules.CopyTrading.Persistence.Ef.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CopyTradingDbContext))]
|
||||||
|
[Migration("20260705120059_InitialCopyTrading")]
|
||||||
|
partial class InitialCopyTrading
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "8.0.13")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("PolyTraderSharp.Models.ClosedTrade", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("TradeId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("AccountId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ClosedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("EntryPrice")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("ExitPrice")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<string>("ExitReason")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDemo")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("MarketQuestion")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("varchar(1000)");
|
||||||
|
|
||||||
|
b.Property<string>("MarketSlug")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(300)
|
||||||
|
.HasColumnType("varchar(300)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("OpenedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Outcome")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<decimal>("PnlPercent")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("RealizedPnl")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<string>("Side")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("varchar(10)");
|
||||||
|
|
||||||
|
b.Property<decimal>("Size")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<int>("SourceTraderId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("TokenId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(120)
|
||||||
|
.HasColumnType("varchar(120)");
|
||||||
|
|
||||||
|
b.Property<decimal>("TotalFees")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.HasKey("TradeId");
|
||||||
|
|
||||||
|
b.HasIndex("AccountId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceTraderId");
|
||||||
|
|
||||||
|
b.HasIndex("TokenId");
|
||||||
|
|
||||||
|
b.ToTable("mod_copytrading_closed_trades", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PolyTraderSharp.Models.CopyTradingAccountSettings", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("AccountId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<decimal>("MaxBuyPrice")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("MaxPriceDifference")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("PerMarketLimit")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("PerMasterLimit")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("PreRedeemLimit")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("ProfitTarget")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("perMaxTime24h")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("perMaxTime6h")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("perMaxTime72h")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("perMaxTimeNone")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.HasKey("AccountId");
|
||||||
|
|
||||||
|
b.ToTable("mod_copytrading_account_settings", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PolyTraderSharp.Models.MasterTraderHistoryRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("varchar(64)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ClosedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("RealizedPnl")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<string>("TokenId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(120)
|
||||||
|
.HasColumnType("varchar(120)");
|
||||||
|
|
||||||
|
b.Property<int>("TraderId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ClosedAt");
|
||||||
|
|
||||||
|
b.HasIndex("TraderId");
|
||||||
|
|
||||||
|
b.ToTable("mod_copytrading_mt_history", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PolyTraderSharp.Models.TrackedTrader", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("AssignedAccountIds")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Category")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("varchar(64)");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("varchar(1000)");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsHidden")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Reasoning")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("varchar(1000)");
|
||||||
|
|
||||||
|
b.Property<double>("TotalPnl")
|
||||||
|
.HasColumnType("double");
|
||||||
|
|
||||||
|
b.Property<int>("TotalTrades")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("WalletAddress")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("varchar(128)");
|
||||||
|
|
||||||
|
b.Property<int>("WinningTrades")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<double>("Winrate30t")
|
||||||
|
.HasColumnType("double");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("mod_copytrading_traders", (string)null);
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PolyTrader.Modules.CopyTrading.Persistence.Ef.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class InitialCopyTrading : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterDatabase()
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "mod_copytrading_account_settings",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
AccountId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
PerMarketLimit = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
MaxPriceDifference = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
MaxBuyPrice = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
ProfitTarget = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
PreRedeemLimit = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
PerMasterLimit = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
perMaxTime6h = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
perMaxTime24h = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
perMaxTime72h = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
perMaxTimeNone = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_mod_copytrading_account_settings", x => x.AccountId);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "mod_copytrading_closed_trades",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
TradeId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
AccountId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
SourceTraderId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
IsDemo = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
TokenId = table.Column<string>(type: "varchar(120)", maxLength: 120, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
MarketSlug = table.Column<string>(type: "varchar(300)", maxLength: 300, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
MarketQuestion = table.Column<string>(type: "varchar(1000)", maxLength: 1000, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Outcome = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Side = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
EntryPrice = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
ExitPrice = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
Size = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
RealizedPnl = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
PnlPercent = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
TotalFees = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
OpenedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
ClosedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
ExitReason = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_mod_copytrading_closed_trades", x => x.TradeId);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "mod_copytrading_mt_history",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<string>(type: "varchar(64)", maxLength: 64, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
TraderId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
TokenId = table.Column<string>(type: "varchar(120)", maxLength: 120, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
RealizedPnl = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||||
|
ClosedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_mod_copytrading_mt_history", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "mod_copytrading_traders",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false),
|
||||||
|
WalletAddress = table.Column<string>(type: "varchar(128)", maxLength: 128, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
DisplayName = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Category = table.Column<string>(type: "varchar(64)", maxLength: 64, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Description = table.Column<string>(type: "varchar(1000)", maxLength: 1000, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Reasoning = table.Column<string>(type: "varchar(1000)", maxLength: 1000, nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
IsHidden = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
TotalTrades = table.Column<int>(type: "int", nullable: false),
|
||||||
|
WinningTrades = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Winrate30t = table.Column<double>(type: "double", nullable: false),
|
||||||
|
TotalPnl = table.Column<double>(type: "double", nullable: false),
|
||||||
|
AssignedAccountIds = table.Column<string>(type: "text", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_mod_copytrading_traders", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_mod_copytrading_closed_trades_AccountId",
|
||||||
|
table: "mod_copytrading_closed_trades",
|
||||||
|
column: "AccountId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_mod_copytrading_closed_trades_SourceTraderId",
|
||||||
|
table: "mod_copytrading_closed_trades",
|
||||||
|
column: "SourceTraderId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_mod_copytrading_closed_trades_TokenId",
|
||||||
|
table: "mod_copytrading_closed_trades",
|
||||||
|
column: "TokenId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_mod_copytrading_mt_history_ClosedAt",
|
||||||
|
table: "mod_copytrading_mt_history",
|
||||||
|
column: "ClosedAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_mod_copytrading_mt_history_TraderId",
|
||||||
|
table: "mod_copytrading_mt_history",
|
||||||
|
column: "TraderId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "mod_copytrading_account_settings");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "mod_copytrading_closed_trades");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "mod_copytrading_mt_history");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "mod_copytrading_traders");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+249
@@ -0,0 +1,249 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using PolyTrader.Modules.CopyTrading.Persistence.Ef;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PolyTrader.Modules.CopyTrading.Persistence.Ef.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CopyTradingDbContext))]
|
||||||
|
partial class CopyTradingDbContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "8.0.13")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("PolyTraderSharp.Models.ClosedTrade", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("TradeId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("AccountId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ClosedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("EntryPrice")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("ExitPrice")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<string>("ExitReason")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDemo")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("MarketQuestion")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("varchar(1000)");
|
||||||
|
|
||||||
|
b.Property<string>("MarketSlug")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(300)
|
||||||
|
.HasColumnType("varchar(300)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("OpenedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Outcome")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<decimal>("PnlPercent")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("RealizedPnl")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<string>("Side")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("varchar(10)");
|
||||||
|
|
||||||
|
b.Property<decimal>("Size")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<int>("SourceTraderId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("TokenId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(120)
|
||||||
|
.HasColumnType("varchar(120)");
|
||||||
|
|
||||||
|
b.Property<decimal>("TotalFees")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.HasKey("TradeId");
|
||||||
|
|
||||||
|
b.HasIndex("AccountId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceTraderId");
|
||||||
|
|
||||||
|
b.HasIndex("TokenId");
|
||||||
|
|
||||||
|
b.ToTable("mod_copytrading_closed_trades", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PolyTraderSharp.Models.CopyTradingAccountSettings", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("AccountId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<decimal>("MaxBuyPrice")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("MaxPriceDifference")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("PerMarketLimit")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("PerMasterLimit")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("PreRedeemLimit")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("ProfitTarget")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("perMaxTime24h")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("perMaxTime6h")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("perMaxTime72h")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("perMaxTimeNone")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.HasKey("AccountId");
|
||||||
|
|
||||||
|
b.ToTable("mod_copytrading_account_settings", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PolyTraderSharp.Models.MasterTraderHistoryRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("varchar(64)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ClosedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<decimal>("RealizedPnl")
|
||||||
|
.HasPrecision(18, 6)
|
||||||
|
.HasColumnType("decimal(18,6)");
|
||||||
|
|
||||||
|
b.Property<string>("TokenId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(120)
|
||||||
|
.HasColumnType("varchar(120)");
|
||||||
|
|
||||||
|
b.Property<int>("TraderId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ClosedAt");
|
||||||
|
|
||||||
|
b.HasIndex("TraderId");
|
||||||
|
|
||||||
|
b.ToTable("mod_copytrading_mt_history", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PolyTraderSharp.Models.TrackedTrader", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("AssignedAccountIds")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Category")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("varchar(64)");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("varchar(1000)");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("varchar(200)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsHidden")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Reasoning")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("varchar(1000)");
|
||||||
|
|
||||||
|
b.Property<double>("TotalPnl")
|
||||||
|
.HasColumnType("double");
|
||||||
|
|
||||||
|
b.Property<int>("TotalTrades")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("WalletAddress")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("varchar(128)");
|
||||||
|
|
||||||
|
b.Property<int>("WinningTrades")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<double>("Winrate30t")
|
||||||
|
.HasColumnType("double");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("mod_copytrading_traders", (string)null);
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using PolyTraderSharp.Models;
|
||||||
|
|
||||||
|
namespace PolyTrader.Modules.CopyTrading.Persistence
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Persistenz der Master-Trader-Historie (geschlossene Trades der Master, für Analytics).
|
||||||
|
/// </summary>
|
||||||
|
public interface IMasterTraderHistoryRepository
|
||||||
|
{
|
||||||
|
void EnsureIndexes();
|
||||||
|
|
||||||
|
/// <summary>Prüft, ob ein Eintrag für Trader + Token im Zeitfenster existiert (Dedup).</summary>
|
||||||
|
bool Exists(int traderId, string tokenId, DateTime windowStart, DateTime windowEnd);
|
||||||
|
|
||||||
|
void Insert(MasterTraderHistoryRecord record);
|
||||||
|
|
||||||
|
List<MasterTraderHistoryRecord> GetByTraderSince(int traderId, DateTime since);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using PolyTraderSharp.Models;
|
||||||
|
|
||||||
|
namespace PolyTrader.Modules.CopyTrading.Persistence
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Persistenz der kopierten Master-Trader (Collection/Tabelle der Tracker).
|
||||||
|
/// Konsolidiert die frühere Inkonsistenz (trackers vs. tracked_traders) auf EINE Quelle.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITrackedTraderRepository
|
||||||
|
{
|
||||||
|
List<TrackedTrader> GetAll();
|
||||||
|
void Upsert(TrackedTrader trader);
|
||||||
|
void Update(TrackedTrader trader);
|
||||||
|
void Delete(int id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,14 @@
|
|||||||
<ProjectReference Include="..\PolyTrader.Core\PolyTrader.Core.csproj" />
|
<ProjectReference Include="..\PolyTrader.Core\PolyTrader.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
|||||||
Reference in New Issue
Block a user