Postgres (#30)

* Add postgres

* Migrate users

* Migrate geneticRequest

* Try to fix Concurrent call

* Fix asyncawait

* Fix async and concurrent

* Migrate backtests

* Add cache for user by address

* Fix backtest migration

* Fix not open connection

* Fix backtest command error

* Fix concurrent

* Fix all concurrency

* Migrate TradingRepo

* Fix scenarios

* Migrate statistic repo

* Save botbackup

* Add settings et moneymanagement

* Add bot postgres

* fix a bit more backups

* Fix bot model

* Fix loading backup

* Remove cache market for read positions

* Add workers to postgre

* Fix workers api

* Reduce get Accounts for workers

* Migrate synth to postgre

* Fix backtest saved

* Remove mongodb

* botservice decorrelation

* Fix tradingbot scope call

* fix tradingbot

* fix concurrent

* Fix scope for genetics

* Fix account over requesting

* Fix bundle backtest worker

* fix a lot of things

* fix tab backtest

* Remove optimized moneymanagement

* Add light signal to not use User and too much property

* Make money management lighter

* insert indicators to awaitable

* Migrate add strategies to await

* Refactor scenario and indicator retrieval to use asynchronous methods throughout the application

* add more async await

* Add services

* Fix and clean

* Fix bot a bit

* Fix bot and add message for cooldown

* Remove fees

* Add script to deploy db

* Update dfeeploy script

* fix script

* Add idempotent script and backup

* finish script migration

* Fix did user and agent name on start bot
This commit is contained in:
Oda
2025-07-27 15:42:17 +02:00
committed by GitHub
parent 361bfbf6e8
commit 422fecea7b
294 changed files with 23953 additions and 7272 deletions

View File

@@ -1,46 +0,0 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Accounts;
using Managing.Infrastructure.Databases.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
using Managing.Infrastructure.Databases.MongoDb.Collections;
namespace Managing.Infrastructure.Databases;
public class AccountRepository : IAccountRepository
{
private readonly IMongoRepository<AccountDto> _accountRepository;
public AccountRepository(IMongoRepository<AccountDto> accountRepository)
{
_accountRepository = accountRepository;
}
public void DeleteAccountByName(string name)
{
var account = _accountRepository.FindOne(a => a.Name == name);
_accountRepository.DeleteById(account.Id.ToString());
}
public async Task<Account> GetAccountByKeyAsync(string key)
{
var account = await _accountRepository.FindOneAsync(a => a.Key == key);
return MongoMappers.Map(account);
}
public async Task<Account> GetAccountByNameAsync(string name)
{
var account = await _accountRepository.FindOneAsync(a => a.Name == name);
return MongoMappers.Map(account);
}
public IEnumerable<Account> GetAccounts()
{
var accounts = _accountRepository.FindAll();
return MongoMappers.Map(accounts);
}
public async Task InsertAccountAsync(Account account)
{
await _accountRepository.InsertOneAsync(MongoMappers.Map(account));
}
}

View File

@@ -1,388 +0,0 @@
using System.Diagnostics;
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Backtests;
using Managing.Domain.Users;
using Managing.Infrastructure.Databases.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
using Managing.Infrastructure.Databases.MongoDb.Collections;
using MongoDB.Driver;
namespace Managing.Infrastructure.Databases;
public class BacktestRepository : IBacktestRepository
{
private readonly IMongoRepository<BacktestDto> _backtestRepository;
private readonly IMongoRepository<BundleBacktestRequestDto> _bundleBacktestRepository;
public BacktestRepository(
IMongoRepository<BacktestDto> backtestRepository,
IMongoRepository<BundleBacktestRequestDto> bundleBacktestRepository)
{
_backtestRepository = backtestRepository;
_bundleBacktestRepository = bundleBacktestRepository;
}
// User-specific operations
public void InsertBacktestForUser(User user, Backtest result)
{
ValidateBacktestData(result);
result.User = user;
var dto = MongoMappers.Map(result);
_backtestRepository.InsertOne(dto);
}
/// <summary>
/// Validates that all numeric fields in the backtest are of the correct type
/// </summary>
private void ValidateBacktestData(Backtest backtest)
{
// Ensure FinalPnl is a valid decimal
if (backtest.FinalPnl.GetType() != typeof(decimal))
{
throw new InvalidOperationException(
$"FinalPnl must be of type decimal, but got {backtest.FinalPnl.GetType().Name}");
}
// Ensure other numeric fields are correct
if (backtest.GrowthPercentage.GetType() != typeof(decimal))
{
throw new InvalidOperationException(
$"GrowthPercentage must be of type decimal, but got {backtest.GrowthPercentage.GetType().Name}");
}
if (backtest.HodlPercentage.GetType() != typeof(decimal))
{
throw new InvalidOperationException(
$"HodlPercentage must be of type decimal, but got {backtest.HodlPercentage.GetType().Name}");
}
if (backtest.Score.GetType() != typeof(double))
{
throw new InvalidOperationException(
$"Score must be of type double, but got {backtest.Score.GetType().Name}");
}
if (backtest.WinRate.GetType() != typeof(int))
{
throw new InvalidOperationException(
$"WinRate must be of type int, but got {backtest.WinRate.GetType().Name}");
}
}
public IEnumerable<Backtest> GetBacktestsByUser(User user)
{
var backtests = _backtestRepository.AsQueryable()
.Where(b => b.User.Name == user.Name)
.ToList();
return backtests.Select(b => MongoMappers.Map(b));
}
public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
{
var backtests = _backtestRepository.AsQueryable()
.Where(b => b.RequestId == requestId)
.ToList();
return backtests.Select(b => MongoMappers.Map(b));
}
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId,
int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var stopwatch = Stopwatch.StartNew();
var collection = _backtestRepository.GetCollection(); // You may need to expose this in your repo
var filter = Builders<BacktestDto>.Filter.Eq(b => b.RequestId, requestId);
var afterQueryMs = stopwatch.ElapsedMilliseconds;
var totalCount = collection.CountDocuments(filter);
var afterCountMs = stopwatch.ElapsedMilliseconds;
var projection = Builders<BacktestDto>.Projection
.Include(b => b.Identifier)
.Include(b => b.FinalPnl)
.Include(b => b.WinRate)
.Include(b => b.GrowthPercentage)
.Include(b => b.HodlPercentage)
.Include(b => b.StartDate)
.Include(b => b.EndDate)
.Include(b => b.Score)
.Include(b => b.ScoreMessage)
.Include(b => b.Config)
.Include(b => b.Fees)
.Include(b => b.Statistics);
// Build sort definition
var sortDefinition = sortBy.ToLower() switch
{
"score" => sortOrder == "desc"
? Builders<BacktestDto>.Sort.Descending(b => b.Score)
: Builders<BacktestDto>.Sort.Ascending(b => b.Score),
"finalpnl" => sortOrder == "desc"
? Builders<BacktestDto>.Sort.Descending(b => b.FinalPnl)
: Builders<BacktestDto>.Sort.Ascending(b => b.FinalPnl),
"winrate" => sortOrder == "desc"
? Builders<BacktestDto>.Sort.Descending(b => b.WinRate)
: Builders<BacktestDto>.Sort.Ascending(b => b.WinRate),
"growthpercentage" => sortOrder == "desc"
? Builders<BacktestDto>.Sort.Descending(b => b.GrowthPercentage)
: Builders<BacktestDto>.Sort.Ascending(b => b.GrowthPercentage),
"hodlpercentage" => sortOrder == "desc"
? Builders<BacktestDto>.Sort.Descending(b => b.HodlPercentage)
: Builders<BacktestDto>.Sort.Ascending(b => b.HodlPercentage),
_ => sortOrder == "desc"
? Builders<BacktestDto>.Sort.Descending(b => b.Score)
: Builders<BacktestDto>.Sort.Ascending(b => b.Score)
};
var afterProjectionMs = stopwatch.ElapsedMilliseconds;
var backtests = collection
.Find(filter)
.Project<BacktestDto>(projection)
.Sort(sortDefinition)
.Skip((page - 1) * pageSize)
.Limit(pageSize)
.ToList();
var afterToListMs = stopwatch.ElapsedMilliseconds;
Console.WriteLine(
$"[BacktestRepo] Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Projection: {afterProjectionMs - afterCountMs}ms, ToList: {afterToListMs - afterProjectionMs}ms, Total: {afterToListMs}ms");
var mappedBacktests = backtests.Select(b => new LightBacktest
{
Id = b.Identifier,
Config = MongoMappers.Map(b.Config),
FinalPnl = b.FinalPnl,
WinRate = b.WinRate,
GrowthPercentage = b.GrowthPercentage,
HodlPercentage = b.HodlPercentage,
StartDate = b.StartDate,
EndDate = b.EndDate,
MaxDrawdown = b.Statistics?.MaxDrawdown,
Fees = b.Fees,
SharpeRatio = b.Statistics?.SharpeRatio != null ? (double)b.Statistics.SharpeRatio : null,
Score = b.Score,
ScoreMessage = b.ScoreMessage ?? string.Empty
});
return (mappedBacktests, (int)totalCount);
}
public Backtest GetBacktestByIdForUser(User user, string id)
{
var backtest = _backtestRepository.FindOne(b => b.Identifier == id);
// Check if backtest exists and belongs to the user
if (backtest != null && backtest.User != null && backtest.User.Name == user.Name)
{
return MongoMappers.Map(backtest);
}
return null;
}
public void DeleteBacktestByIdForUser(User user, string id)
{
var backtest = _backtestRepository.FindOne(b => b.Identifier == id);
if (backtest != null && backtest.User != null && backtest.User.Name == user.Name)
{
_backtestRepository.DeleteById(backtest.Id.ToString());
}
}
public void DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids)
{
var backtests = _backtestRepository.AsQueryable()
.Where(b => b.User != null && b.User.Name == user.Name && ids.Contains(b.Identifier))
.ToList();
foreach (var backtest in backtests)
{
_backtestRepository.DeleteById(backtest.Id.ToString());
}
}
public void DeleteAllBacktestsForUser(User user)
{
var backtests = _backtestRepository.AsQueryable()
.Where(b => b.User != null && b.User.Name == user.Name)
.ToList();
foreach (var backtest in backtests)
{
_backtestRepository.DeleteById(backtest.Id.ToString());
}
}
public void DeleteBacktestsByRequestId(string requestId)
{
var backtests = _backtestRepository.AsQueryable()
.Where(b => b.RequestId == requestId)
.ToList();
foreach (var backtest in backtests)
{
_backtestRepository.DeleteById(backtest.Id.ToString());
}
}
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page,
int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var stopwatch = Stopwatch.StartNew();
var collection = _backtestRepository.GetCollection();
var filter = Builders<BacktestDto>.Filter.Eq(b => b.User.Name, user.Name);
var afterQueryMs = stopwatch.ElapsedMilliseconds;
var totalCount = collection.CountDocuments(filter);
var afterCountMs = stopwatch.ElapsedMilliseconds;
var projection = Builders<BacktestDto>.Projection
.Include(b => b.Identifier)
.Include(b => b.FinalPnl)
.Include(b => b.WinRate)
.Include(b => b.GrowthPercentage)
.Include(b => b.HodlPercentage)
.Include(b => b.StartDate)
.Include(b => b.EndDate)
.Include(b => b.Score)
.Include(b => b.ScoreMessage)
.Include(b => b.Config)
.Include(b => b.Fees)
.Include(b => b.Statistics);
// Build sort definition
var sortDefinition = sortBy.ToLower() switch
{
"score" => sortOrder == "desc"
? Builders<BacktestDto>.Sort.Descending(b => b.Score)
: Builders<BacktestDto>.Sort.Ascending(b => b.Score),
"finalpnl" => sortOrder == "desc"
? Builders<BacktestDto>.Sort.Descending(b => b.FinalPnl)
: Builders<BacktestDto>.Sort.Ascending(b => b.FinalPnl),
"winrate" => sortOrder == "desc"
? Builders<BacktestDto>.Sort.Descending(b => b.WinRate)
: Builders<BacktestDto>.Sort.Ascending(b => b.WinRate),
"growthpercentage" => sortOrder == "desc"
? Builders<BacktestDto>.Sort.Descending(b => b.GrowthPercentage)
: Builders<BacktestDto>.Sort.Ascending(b => b.GrowthPercentage),
"hodlpercentage" => sortOrder == "desc"
? Builders<BacktestDto>.Sort.Descending(b => b.HodlPercentage)
: Builders<BacktestDto>.Sort.Ascending(b => b.HodlPercentage),
_ => sortOrder == "desc"
? Builders<BacktestDto>.Sort.Descending(b => b.Score)
: Builders<BacktestDto>.Sort.Ascending(b => b.Score)
};
var afterProjectionMs = stopwatch.ElapsedMilliseconds;
var backtests = collection
.Find(filter)
.Project<BacktestDto>(projection)
.Sort(sortDefinition)
.Skip((page - 1) * pageSize)
.Limit(pageSize)
.ToList();
var afterToListMs = stopwatch.ElapsedMilliseconds;
Console.WriteLine(
$"[BacktestRepo] User Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Projection: {afterProjectionMs - afterCountMs}ms, ToList: {afterToListMs - afterProjectionMs}ms, Total: {afterToListMs}ms");
var mappedBacktests = backtests.Select(b => new LightBacktest
{
Id = b.Identifier,
Config = MongoMappers.Map(b.Config),
FinalPnl = b.FinalPnl,
WinRate = b.WinRate,
GrowthPercentage = b.GrowthPercentage,
HodlPercentage = b.HodlPercentage,
StartDate = b.StartDate,
EndDate = b.EndDate,
MaxDrawdown = b.Statistics?.MaxDrawdown,
Fees = b.Fees,
SharpeRatio = b.Statistics?.SharpeRatio != null ? (double)b.Statistics.SharpeRatio : null,
Score = b.Score,
ScoreMessage = b.ScoreMessage ?? string.Empty
});
return (mappedBacktests, (int)totalCount);
}
// Bundle backtest methods
public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
{
bundleRequest.User = user;
var dto = MongoMappers.Map(bundleRequest);
_bundleBacktestRepository.InsertOne(dto);
}
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user)
{
var projection = Builders<BundleBacktestRequestDto>.Projection
.Include(b => b.RequestId)
.Include(b => b.Status)
.Include(b => b.CreatedAt)
.Include(b => b.CurrentBacktest)
.Include(b => b.EstimatedTimeRemainingSeconds)
.Include(b => b.TotalBacktests)
.Include(b => b.CurrentBacktest)
.Include(b => b.CompletedAt)
.Include(b => b.ErrorMessage)
.Include(b => b.ProgressInfo)
.Include(b => b.Name)
.Include(b => b.CompletedBacktests)
.Include(b => b.User);
var filter = Builders<BundleBacktestRequestDto>.Filter.Eq(b => b.User.Name, user.Name);
var bundleRequests = _bundleBacktestRepository.GetCollection()
.Find(filter)
.Project<BundleBacktestRequestDto>(projection)
.ToList();
return bundleRequests.Select(MongoMappers.Map);
}
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id)
{
var bundleRequest = _bundleBacktestRepository.FindOne(b => b.RequestId == id && b.User.Name == user.Name);
if (bundleRequest != null && bundleRequest.User.Name == user.Name)
{
return MongoMappers.Map(bundleRequest);
}
return null;
}
public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest)
{
var existingRequest = _bundleBacktestRepository.FindOne(b => b.RequestId == bundleRequest.RequestId);
if (existingRequest != null)
{
var dto = MongoMappers.Map(bundleRequest);
dto.Id = existingRequest.Id; // Preserve the MongoDB ObjectId
_bundleBacktestRepository.ReplaceOne(dto);
}
}
public void DeleteBundleBacktestRequestByIdForUser(User user, string id)
{
var bundleRequest = _bundleBacktestRepository.FindOne(b => b.RequestId == id);
if (bundleRequest != null && bundleRequest.User.Name == user.Name)
{
_bundleBacktestRepository.DeleteById(bundleRequest.Id.ToString());
}
}
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status)
{
var requests = _bundleBacktestRepository.AsQueryable()
.Where(b => b.Status == status)
.ToList();
return requests.Select(MongoMappers.Map);
}
}

View File

@@ -1,42 +0,0 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Bots;
using Managing.Infrastructure.Databases.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
using Managing.Infrastructure.Databases.MongoDb.Collections;
namespace Managing.Infrastructure.Databases;
public class BotRepository : IBotRepository
{
private readonly IMongoRepository<BotDto> _botRepository;
public BotRepository(IMongoRepository<BotDto> botRepository)
{
_botRepository = botRepository;
}
public async Task InsertBotAsync(BotBackup bot)
{
await _botRepository.InsertOneAsync(MongoMappers.Map(bot));
}
public IEnumerable<BotBackup> GetBots()
{
var bots = _botRepository.FindAll();
return bots.Select(b => MongoMappers.Map(b));
}
public async Task UpdateBackupBot(BotBackup bot)
{
var b = await _botRepository.FindOneAsync(b => b.Identifier == bot.Identifier);
var dto = MongoMappers.Map(bot);
dto.Id = b.Id;
_botRepository.Update(dto);
}
public async Task DeleteBotBackup(string identifier)
{
var backup = await _botRepository.FindOneAsync(b => b.Identifier == identifier);
await _botRepository.DeleteOneAsync(b => b.Id == backup.Id);
}
}

View File

@@ -1,90 +0,0 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Backtests;
using Managing.Domain.Users;
using Managing.Infrastructure.Databases.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
using Managing.Infrastructure.Databases.MongoDb.Collections;
namespace Managing.Infrastructure.Databases;
public class GeneticRepository : IGeneticRepository
{
private readonly IMongoRepository<GeneticRequestDto> _geneticRequestRepository;
public GeneticRepository(IMongoRepository<GeneticRequestDto> geneticRequestRepository)
{
_geneticRequestRepository = geneticRequestRepository;
}
public void InsertGeneticRequestForUser(User user, GeneticRequest geneticRequest)
{
geneticRequest.User = user;
_geneticRequestRepository.InsertOne(MongoMappers.Map(geneticRequest));
}
public IEnumerable<GeneticRequest> GetGeneticRequestsByUser(User user)
{
var geneticRequests = _geneticRequestRepository.AsQueryable()
.Where(gr => gr.User.Name == user.Name)
.OrderByDescending(gr => gr.CreatedAt)
.ToList();
return geneticRequests.Select(gr => MongoMappers.Map(gr));
}
public GeneticRequest GetGeneticRequestByIdForUser(User user, string id)
{
var geneticRequest = _geneticRequestRepository.FindById(id);
// Check if genetic request exists and belongs to the user
if (geneticRequest != null && geneticRequest.User != null && geneticRequest.User.Name == user.Name)
{
return MongoMappers.Map(geneticRequest);
}
return null;
}
public void UpdateGeneticRequest(GeneticRequest geneticRequest)
{
var existingRequest = _geneticRequestRepository.FindOne(gr => gr.RequestId == geneticRequest.RequestId);
if (existingRequest != null)
{
var updatedDto = MongoMappers.Map(geneticRequest);
updatedDto.Id = existingRequest.Id; // Preserve the MongoDB ObjectId
_geneticRequestRepository.ReplaceOne(updatedDto);
}
}
public void DeleteGeneticRequestByIdForUser(User user, string id)
{
var geneticRequest = _geneticRequestRepository.FindOne(gr => gr.RequestId == id);
if (geneticRequest != null && geneticRequest.User != null && geneticRequest.User.Name == user.Name)
{
_geneticRequestRepository.DeleteById(geneticRequest.Id.ToString());
}
}
public void DeleteAllGeneticRequestsForUser(User user)
{
var geneticRequests = _geneticRequestRepository.AsQueryable()
.Where(gr => gr.User != null && gr.User.Name == user.Name)
.ToList();
foreach (var geneticRequest in geneticRequests)
{
_geneticRequestRepository.DeleteById(geneticRequest.Id.ToString());
}
}
public IEnumerable<GeneticRequest> GetPendingGeneticRequests()
{
var pendingRequests = _geneticRequestRepository.AsQueryable()
.Where(gr => gr.Status == "Pending")
.OrderBy(gr => gr.CreatedAt)
.ToList();
return pendingRequests.Select(gr => MongoMappers.Map(gr));
}
}

View File

@@ -19,11 +19,6 @@ public static class PriceHelpers
CloseTime = candle.Date,
High = candle.High,
Low = candle.Low,
BaseVolume = candle.BaseVolume,
QuoteVolume = candle.QuoteVolume,
TradeCount = candle.TradeCount,
TakerBuyBaseVolume = candle.TakerBuyBaseVolume,
TakerBuyQuoteVolume = candle.TakerBuyQuoteVolume,
Timeframe = candle.Timeframe.ToString()
};
@@ -42,11 +37,6 @@ public static class PriceHelpers
Date = dto.CloseTime,
High = dto.High,
Low = dto.Low,
BaseVolume = dto.BaseVolume,
QuoteVolume = dto.QuoteVolume,
TradeCount = dto.TradeCount,
TakerBuyBaseVolume = dto.TakerBuyBaseVolume,
TakerBuyQuoteVolume = dto.TakerBuyQuoteVolume,
Timeframe = MiscExtensions.ParseEnum<Timeframe>(dto.Timeframe)
};
}

View File

@@ -10,11 +10,17 @@
<PackageReference Include="InfluxDB.Client" Version="4.14.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1"/>
<PackageReference Include="MongoDB.Bson" Version="2.25.0"/>
<PackageReference Include="MongoDB.Driver" Version="2.25.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11"/>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.10"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Managing.Application.Workers\Managing.Application.Workers.csproj"/>
</ItemGroup>
<ItemGroup>
<Folder Include="Migrations\"/>
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class UpdateBotBackupDataToText : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Data",
table: "BotBackups",
type: "text",
nullable: false,
oldClrType: typeof(string),
oldType: "jsonb");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Data",
table: "BotBackups",
type: "jsonb",
nullable: false,
oldClrType: typeof(string),
oldType: "text");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class AddWorkerEntity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Workers",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
WorkerType = table.Column<string>(type: "text", nullable: false),
StartTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
LastRunTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
ExecutionCount = table.Column<int>(type: "integer", nullable: false),
DelayTicks = table.Column<long>(type: "bigint", nullable: false),
IsActive = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Workers", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Workers_WorkerType",
table: "Workers",
column: "WorkerType",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Workers");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,85 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class AddSynthEntities : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "SynthMinersLeaderboards",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Asset = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
TimeIncrement = table.Column<int>(type: "integer", nullable: false),
SignalDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
IsBacktest = table.Column<bool>(type: "boolean", nullable: false),
MinersData = table.Column<string>(type: "jsonb", nullable: false),
CacheKey = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SynthMinersLeaderboards", x => x.Id);
});
migrationBuilder.CreateTable(
name: "SynthPredictions",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Asset = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
MinerUid = table.Column<int>(type: "integer", nullable: false),
TimeIncrement = table.Column<int>(type: "integer", nullable: false),
TimeLength = table.Column<int>(type: "integer", nullable: false),
SignalDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
IsBacktest = table.Column<bool>(type: "boolean", nullable: false),
PredictionData = table.Column<string>(type: "jsonb", nullable: false),
CacheKey = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SynthPredictions", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_SynthMinersLeaderboards_CacheKey",
table: "SynthMinersLeaderboards",
column: "CacheKey",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_SynthMinersLeaderboards_CreatedAt",
table: "SynthMinersLeaderboards",
column: "CreatedAt");
migrationBuilder.CreateIndex(
name: "IX_SynthPredictions_CacheKey",
table: "SynthPredictions",
column: "CacheKey",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_SynthPredictions_CreatedAt",
table: "SynthPredictions",
column: "CreatedAt");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "SynthMinersLeaderboards");
migrationBuilder.DropTable(
name: "SynthPredictions");
}
}
}

View File

@@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class UpdateScoreMessageToText : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "ScoreMessage",
table: "Backtests",
type: "text",
maxLength: 1000,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(1000)",
oldMaxLength: 1000);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "ScoreMessage",
table: "Backtests",
type: "character varying(1000)",
maxLength: 1000,
nullable: false,
oldClrType: typeof(string),
oldType: "text",
oldMaxLength: 1000);
}
}
}

View File

@@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class AddUserIdToBundleBacktestRequest : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "UserId",
table: "BundleBacktestRequests",
type: "integer",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_BundleBacktestRequests_UserId",
table: "BundleBacktestRequests",
column: "UserId");
migrationBuilder.AddForeignKey(
name: "FK_BundleBacktestRequests_Users_UserId",
table: "BundleBacktestRequests",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_BundleBacktestRequests_Users_UserId",
table: "BundleBacktestRequests");
migrationBuilder.DropIndex(
name: "IX_BundleBacktestRequests_UserId",
table: "BundleBacktestRequests");
migrationBuilder.DropColumn(
name: "UserId",
table: "BundleBacktestRequests");
}
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class RemoveOptimizedMoneyManagementJsonFromBacktestEntity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "OptimizedMoneyManagementJson",
table: "Backtests");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "OptimizedMoneyManagementJson",
table: "Backtests",
type: "jsonb",
nullable: false,
defaultValue: "");
}
}
}

View File

@@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class AddUserIdToMoneyManagement : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "UserId",
table: "MoneyManagements",
type: "integer",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_MoneyManagements_UserId",
table: "MoneyManagements",
column: "UserId");
migrationBuilder.AddForeignKey(
name: "FK_MoneyManagements_Users_UserId",
table: "MoneyManagements",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_MoneyManagements_Users_UserId",
table: "MoneyManagements");
migrationBuilder.DropIndex(
name: "IX_MoneyManagements_UserId",
table: "MoneyManagements");
migrationBuilder.DropColumn(
name: "UserId",
table: "MoneyManagements");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class AddUserIdToBotBackup : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "UserId",
table: "BotBackups",
type: "integer",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_BotBackups_UserId",
table: "BotBackups",
column: "UserId");
migrationBuilder.AddForeignKey(
name: "FK_BotBackups_Users_UserId",
table: "BotBackups",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_BotBackups_Users_UserId",
table: "BotBackups");
migrationBuilder.DropIndex(
name: "IX_BotBackups_UserId",
table: "BotBackups");
migrationBuilder.DropColumn(
name: "UserId",
table: "BotBackups");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,51 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class RemoveFeeEntity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Fees");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Fees",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Cost = table.Column<decimal>(type: "numeric(18,8)", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Exchange = table.Column<string>(type: "text", nullable: false),
LastUpdate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Fees", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Fees_Exchange",
table: "Fees",
column: "Exchange",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Fees_LastUpdate",
table: "Fees",
column: "LastUpdate");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +0,0 @@
using System.Linq.Expressions;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using MongoDB.Driver;
namespace Managing.Infrastructure.Databases.MongoDb.Abstractions
{
public interface IMongoRepository<TDocument> where TDocument : IDocument
{
IQueryable<TDocument> AsQueryable();
IEnumerable<TDocument> FilterBy(
Expression<Func<TDocument, bool>> filterExpression);
IEnumerable<TDocument> FindAll();
IEnumerable<TDocument> FilterBy(FilterDefinition<TDocument> filter);
IEnumerable<TProjected> FilterBy<TProjected>(
Expression<Func<TDocument, bool>> filterExpression,
Expression<Func<TDocument, TProjected>> projectionExpression);
TDocument FindOne(Expression<Func<TDocument, bool>> filterExpression);
Task<TDocument> FindOneAsync(Expression<Func<TDocument, bool>> filterExpression);
TDocument FindById(string id);
Task<TDocument> FindByIdAsync(string id);
void InsertOne(TDocument document);
Task InsertOneAsync(TDocument document);
void InsertMany(ICollection<TDocument> documents);
Task InsertManyAsync(ICollection<TDocument> documents);
void ReplaceOne(TDocument document);
Task ReplaceOneAsync(TDocument document);
void DeleteOne(Expression<Func<TDocument, bool>> filterExpression);
Task DeleteOneAsync(Expression<Func<TDocument, bool>> filterExpression);
void DeleteById(string id);
Task DeleteByIdAsync(string id);
void DeleteMany(Expression<Func<TDocument, bool>> filterExpression);
Task DeleteManyAsync(Expression<Func<TDocument, bool>> filterExpression);
void Update(TDocument entity);
void CreateIndex(string column);
void DropCollection();
IMongoCollection<TDocument> GetCollection();
}
}

View File

@@ -1,13 +0,0 @@
namespace Managing.Infrastructure.Databases.MongoDb.Attributes
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class BsonCollectionAttribute : Attribute
{
public string CollectionName { get; }
public BsonCollectionAttribute(string collectionName)
{
CollectionName = collectionName;
}
}
}

View File

@@ -1,16 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
[BsonCollection("Accounts")]
public class AccountDto : Document
{
public UserDto User { get; set; }
public string Name { get; set; }
public TradingExchanges Exchanges { get; set; }
public AccountType Type { get; set; }
public string Key { get; set; }
public string Secret { get; set; }
}

View File

@@ -1,37 +0,0 @@
using Exilion.TradingAtomics;
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Managing.Infrastructure.Databases.MongoDb.Collections
{
[BsonCollection("Backtests")]
public class BacktestDto : Document
{
[BsonRepresentation(BsonType.Decimal128)]
public decimal FinalPnl { get; set; }
public int WinRate { get; set; }
[BsonRepresentation(BsonType.Decimal128)]
public decimal GrowthPercentage { get; set; }
[BsonRepresentation(BsonType.Decimal128)]
public decimal HodlPercentage { get; set; }
public TradingBotConfigDto Config { get; set; }
public List<PositionDto> Positions { get; set; }
public List<SignalDto> Signals { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public MoneyManagementDto MoneyManagement { get; internal set; }
public MoneyManagementDto OptimizedMoneyManagement { get; internal set; }
public UserDto User { get; set; }
public PerformanceMetrics Statistics { get; set; }
[BsonRepresentation(BsonType.Decimal128)]
public decimal Fees { get; set; }
public double Score { get; set; }
public string ScoreMessage { get; set; } = string.Empty;
public string Identifier { get; set; }
public string RequestId { get; set; }
public string? Metadata { get; set; }
}
}

View File

@@ -1,17 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
[BsonCollection("BadTrader")]
public class BadTraderDto : Document
{
public string Address { get; set; }
public int Winrate { get; set; }
public decimal Pnl { get; set; }
public int TradeCount { get; set; }
public decimal AverageWin { get; set; }
public decimal AverageLoss { get; set; }
public decimal Roi { get; set; }
}

View File

@@ -1,16 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
[BsonCollection("BestTrader")]
public class BestTraderDto : Document
{
public string Address { get; set; }
public int Winrate { get; set; }
public decimal Pnl { get; set; }
public int TradeCount { get; set; }
public decimal AverageWin { get; set; }
public decimal AverageLoss { get; set; }
public decimal Roi { get; set; }
}

View File

@@ -1,14 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
[BsonCollection("Bots")]
public class BotDto : Document
{
public string Data { get; set; }
public string Identifier { get; set; }
public UserDto User { get; set; }
public BotStatus LastStatus { get; set; }
}

View File

@@ -1,24 +0,0 @@
using Managing.Domain.Backtests;
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
[BsonCollection("BundleBacktestRequests")]
public class BundleBacktestRequestDto : Document
{
public string RequestId { get; set; } = string.Empty;
public UserDto User { get; set; } = new();
public DateTime? CompletedAt { get; set; }
public BundleBacktestRequestStatus Status { get; set; }
public string BacktestRequestsJson { get; set; } = string.Empty;
public int TotalBacktests { get; set; }
public int CompletedBacktests { get; set; }
public int FailedBacktests { get; set; }
public string? ErrorMessage { get; set; }
public string? ProgressInfo { get; set; }
public string? CurrentBacktest { get; set; }
public int? EstimatedTimeRemainingSeconds { get; set; }
public string Name { get; set; } = string.Empty;
public List<string> Results { get; set; } = new();
}

View File

@@ -1,28 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using MongoDB.Bson.Serialization.Attributes;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections
{
[BsonCollection("Candles")]
public class CandleDto : Document
{
public TradingExchanges Exchange { get; set; }
public Timeframe Timeframe { get; set; }
public string Ticker { get; set; }
[BsonDateTimeOptions]
public DateTime OpenTime { get; set; }
[BsonDateTimeOptions]
public DateTime CloseTime { get; set; }
public decimal Open { get; set; }
public decimal Close { get; set; }
public decimal High { get; set; }
public decimal Low { get; set; }
public decimal BaseVolume { get; set; }
public decimal QuoteVolume { get; set; }
public int TradeCount { get; set; }
public decimal TakerBuyBaseVolume { get; set; }
public decimal TakerBuyQuoteVolume { get; set; }
}
}

View File

@@ -1,13 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
[BsonCollection("Fees")]
public class FeeDto : Document
{
public decimal Cost { get; set; }
public TradingExchanges Exchange { get; set; }
public DateTime LastUpdate { get; set; }
}

View File

@@ -1,15 +0,0 @@
using Managing.Common;
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
[BsonCollection("FundingRates")]
public class FundingRateDto : Document
{
public Enums.Ticker Ticker { get; set; }
public decimal Rate { get; set; }
public Enums.TradingExchanges Exchange { get; set; }
public DateTime Date { get; set; }
public Enums.TradeDirection Direction { get; set; }
}

View File

@@ -1,37 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections
{
[BsonCollection("GeneticRequests")]
public class GeneticRequestDto : Document
{
public string RequestId { get; set; }
public UserDto User { get; set; }
public DateTime? CompletedAt { get; set; }
public string Status { get; set; }
public Ticker Ticker { get; set; }
public Timeframe Timeframe { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public decimal Balance { get; set; }
public int PopulationSize { get; set; }
public int Generations { get; set; }
public double MutationRate { get; set; }
public GeneticSelectionMethod SelectionMethod { get; set; }
public GeneticCrossoverMethod CrossoverMethod { get; set; }
public GeneticMutationMethod MutationMethod { get; set; }
public int ElitismPercentage { get; set; }
public double MaxTakeProfit { get; set; }
public List<IndicatorType> EligibleIndicators { get; set; } = new();
public double? BestFitness { get; set; }
public string? BestIndividual { get; set; }
public string? ErrorMessage { get; set; }
public string? ProgressInfo { get; set; }
public string? BestChromosome { get; set; }
public double? BestFitnessSoFar { get; set; }
public int CurrentGeneration { get; set; }
public DateTime? UpdatedAt { get; set; }
}
}

View File

@@ -1,25 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections
{
[BsonCollection("Indicators")]
public class IndicatorDto : Document
{
public IndicatorType Type { get; set; }
public Timeframe Timeframe { get; set; }
public string Name { get; set; }
public int MinimumHistory { get; set; }
public int? Period { get; set; }
public int? FastPeriods { get; set; }
public int? SlowPeriods { get; set; }
public int? SignalPeriods { get; set; }
public double? Multiplier { get; set; }
public int? StochPeriods { get; set; }
public int? SmoothPeriods { get; set; }
public int? CyclePeriods { get; set; }
public SignalType SignalType { get; set; }
public UserDto User { get; set; }
}
}

View File

@@ -1,19 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections
{
[BsonCollection("MoneyManagement")]
public class MoneyManagementDto : Document
{
public Timeframe Timeframe { get; set; }
public RiskLevel RiskLevel { get; set; }
public decimal BalanceAtRisk { get; set; }
public decimal StopLoss { get; set; }
public decimal TakeProfit { get; set; }
public decimal Leverage { get; set; }
public string Name { get; internal set; }
public UserDto User { get; set; }
}
}

View File

@@ -1,28 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using MongoDB.Bson.Serialization.Attributes;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections
{
[BsonCollection("Positions")]
public class PositionDto : Document
{
[BsonDateTimeOptions]
public DateTime Date { get; set; }
public TradeDto Open { get; set; }
public TradeDto StopLoss { get; set; }
public TradeDto TakeProfit1 { get; set; }
public TradeDto TakeProfit2 { get; set; }
public decimal ProfitAndLoss { get; set; }
public TradeDirection OriginDirection { get; set; }
public string Identifier { get; set; }
public PositionStatus Status { get; set; }
public Ticker Ticker { get; set; }
public string SignalIdentifier { get; set; }
public string AccountName { get; set; }
public MoneyManagementDto MoneyManagement { get; set; }
public PositionInitiator Initiator { get; set; }
public UserDto User { get; set; }
}
}

View File

@@ -1,22 +0,0 @@
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections
{
public class RiskManagementDto
{
public decimal AdverseProbabilityThreshold { get; set; }
public decimal FavorableProbabilityThreshold { get; set; }
public decimal RiskAversion { get; set; }
public decimal KellyMinimumThreshold { get; set; }
public decimal KellyMaximumCap { get; set; }
public decimal MaxLiquidationProbability { get; set; }
public int SignalValidationTimeHorizonHours { get; set; }
public int PositionMonitoringTimeHorizonHours { get; set; }
public decimal PositionWarningThreshold { get; set; }
public decimal PositionAutoCloseThreshold { get; set; }
public decimal KellyFractionalMultiplier { get; set; }
public RiskToleranceLevel RiskTolerance { get; set; }
public bool UseExpectedUtility { get; set; }
public bool UseKellyCriterion { get; set; }
}
}

View File

@@ -1,14 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
namespace Managing.Infrastructure.Databases.MongoDb.Collections
{
[BsonCollection("Scenarios")]
public class ScenarioDto : Document
{
public string Name { get; set; }
public List<IndicatorDto> Indicators { get; set; }
public int LoopbackPeriod { get; set; }
public UserDto User { get; set; }
}
}

View File

@@ -1,23 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections
{
[BsonCollection("Signals")]
public class SignalDto : Document
{
public TradeDirection Direction { get; set; }
public Confidence Confidence { get; set; }
public DateTime Date { get; set; }
public CandleDto Candle { get; set; }
public string Identifier { get; set; }
public Ticker Ticker { get; set; }
public SignalStatus Status { get; set; }
public Timeframe Timeframe { get; set; }
public IndicatorType Type { get; set; }
public SignalType SignalType { get; set; }
public UserDto User { get; set; }
public string IndicatorName { get; set; }
}
}

View File

@@ -1,30 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
[BsonCollection("SpotlighOverview")]
public class SpotlighOverviewDto : Document
{
public List<SpotlightDto> Spotlights { get; set; }
public DateTime DateTime { get; set; }
public Guid Identifier { get; set; }
public int ScenarioCount { get; set; }
}
public class SpotlightDto
{
public ScenarioDto Scenario { get; set; }
public List<TickerSignalDto> TickerSignals { get; set; }
}
public class TickerSignalDto
{
public Ticker Ticker { get; set; }
public List<SignalDto> FiveMinutes { get; set; }
public List<SignalDto> FifteenMinutes { get; set; }
public List<SignalDto> OneHour { get; set; }
public List<SignalDto> FourHour { get; set; }
public List<SignalDto> OneDay { get; set; }
}

View File

@@ -1,42 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
/// <summary>
/// MongoDB DTO for storing Synth miners leaderboard data
/// </summary>
[BsonCollection("SynthMinersLeaderboard")]
public class SynthMinersLeaderboardDto : Document
{
/// <summary>
/// Asset symbol (e.g., "BTC", "ETH")
/// </summary>
public string Asset { get; set; }
/// <summary>
/// Time increment used for this leaderboard data
/// </summary>
public int TimeIncrement { get; set; }
/// <summary>
/// Signal date for which this leaderboard was retrieved (for backtests)
/// Null for live trading data
/// </summary>
public DateTime? SignalDate { get; set; }
/// <summary>
/// Whether this is backtest data or live data
/// </summary>
public bool IsBacktest { get; set; }
/// <summary>
/// Serialized JSON of miners list
/// </summary>
public string MinersData { get; set; }
/// <summary>
/// Cache key for quick lookup
/// </summary>
public string CacheKey { get; set; }
}

View File

@@ -1,57 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
/// <summary>
/// MongoDB DTO for storing Synth miners predictions data
/// </summary>
[BsonCollection("SynthMinersPredictions")]
public class SynthMinersPredictionsDto : Document
{
/// <summary>
/// Asset symbol (e.g., "BTC", "ETH")
/// </summary>
public string Asset { get; set; }
/// <summary>
/// Time increment used for these predictions
/// </summary>
public int TimeIncrement { get; set; }
/// <summary>
/// Time length (horizon) for these predictions in seconds
/// </summary>
public int TimeLength { get; set; }
/// <summary>
/// Signal date for which these predictions were retrieved (for backtests)
/// Null for live trading data
/// </summary>
public DateTime? SignalDate { get; set; }
/// <summary>
/// Whether this is backtest data or live data
/// </summary>
public bool IsBacktest { get; set; }
/// <summary>
/// Serialized JSON of miner UIDs list
/// </summary>
public string MinerUidsData { get; set; }
/// <summary>
/// Serialized JSON of predictions data
/// </summary>
public string PredictionsData { get; set; }
/// <summary>
/// When this prediction data was fetched from the API
/// </summary>
public DateTime FetchedAt { get; set; }
/// <summary>
/// Cache key for quick lookup
/// </summary>
public string CacheKey { get; set; }
}

View File

@@ -1,52 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
/// <summary>
/// MongoDB DTO for storing individual Synth miner prediction data
/// </summary>
[BsonCollection("SynthPredictions")]
public class SynthPredictionDto : Document
{
/// <summary>
/// Asset symbol (e.g., "BTC", "ETH")
/// </summary>
public string Asset { get; set; }
/// <summary>
/// Miner UID that provided this prediction
/// </summary>
public int MinerUid { get; set; }
/// <summary>
/// Time increment used for this prediction
/// </summary>
public int TimeIncrement { get; set; }
/// <summary>
/// Time length (horizon) for this prediction in seconds
/// </summary>
public int TimeLength { get; set; }
/// <summary>
/// Signal date for which this prediction was retrieved (for backtests)
/// Null for live trading data
/// </summary>
public DateTime? SignalDate { get; set; }
/// <summary>
/// Whether this is backtest data or live data
/// </summary>
public bool IsBacktest { get; set; }
/// <summary>
/// Serialized JSON of the prediction data
/// </summary>
public string PredictionData { get; set; }
/// <summary>
/// Cache key for quick lookup
/// </summary>
public string CacheKey { get; set; }
}

View File

@@ -1,15 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
[BsonCollection("TopVolumeTickers")]
public class TopVolumeTickerDto : Document
{
public Ticker Ticker { get; set; }
public DateTime Date { get; set; }
public decimal Volume { get; set; }
public int Rank { get; set; }
public TradingExchanges Exchange { get; set; }
}

View File

@@ -1,24 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using MongoDB.Bson.Serialization.Attributes;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections
{
[BsonCollection("Trades")]
public class TradeDto : Document
{
[BsonDateTimeOptions]
public DateTime Date { get; set; }
public TradeDirection Direction { get; set; }
public TradeStatus Status { get; set; }
public TradeType TradeType { get; set; }
public Ticker Ticker { get; set; }
public decimal Fee { get; set; }
public decimal Quantity { get; set; }
public decimal Price { get; set; }
public decimal Leverage { get; set; }
public string ExchangeOrderId { get; set; }
public string Message { get; set; }
}
}

View File

@@ -1,29 +0,0 @@
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections
{
public class TradingBotConfigDto
{
public string AccountName { get; set; }
public MoneyManagementDto MoneyManagement { get; set; }
public Ticker Ticker { get; set; }
public Timeframe Timeframe { get; set; }
public bool IsForWatchingOnly { get; set; }
public decimal BotTradingBalance { get; set; }
public bool IsForBacktest { get; set; }
public int CooldownPeriod { get; set; }
public int MaxLossStreak { get; set; }
public bool FlipPosition { get; set; }
public string Name { get; set; }
public RiskManagementDto RiskManagement { get; set; }
public ScenarioDto Scenario { get; set; }
public string ScenarioName { get; set; }
public decimal? MaxPositionTimeHours { get; set; }
public bool CloseEarlyWhenProfitable { get; set; }
public bool FlipOnlyWhenInProfit { get; set; }
public bool UseSynthApi { get; set; }
public bool UseForPositionSizing { get; set; }
public bool UseForSignalFiltering { get; set; }
public bool UseForDynamicStopLoss { get; set; }
}
}

View File

@@ -1,13 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
[BsonCollection("Users")]
public class UserDto : Document
{
public string Name { get; set; }
public string AgentName { get; set; }
public string AvatarUrl { get; set; }
public string TelegramChannel { get; set; }
}

View File

@@ -1,16 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
[BsonCollection("Workers")]
public class WorkerDto : Document
{
public WorkerType WorkerType { get; set; }
public DateTime StartTime { get; set; }
public DateTime? LastRunTime { get; set; }
public int ExecutionCount { get; set; }
public TimeSpan Delay { get; set; }
public bool IsActive { get; set; }
}

View File

@@ -1,16 +0,0 @@
using Managing.Domain.Workflows.Synthetics;
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
[BsonCollection("Workflow")]
public class WorkflowDto : Document
{
public string Name { get; set; }
public string Description { get; set; }
public WorkflowUsage Usage { get; set; }
public List<SyntheticFlow> Flows { get; set; }
}

View File

@@ -1,13 +0,0 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Managing.Infrastructure.Databases.MongoDb.Configurations
{
public abstract class Document : IDocument
{
[BsonId]
public ObjectId Id { get; set; }
[BsonDateTimeOptions]
public DateTime CreatedAt => Id.CreationTime;
}
}

View File

@@ -1,14 +0,0 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Managing.Infrastructure.Databases.MongoDb.Configurations
{
public interface IDocument
{
[BsonId]
[BsonRepresentation(BsonType.String)]
ObjectId Id { get; set; }
DateTime CreatedAt { get; }
}
}

View File

@@ -1,8 +0,0 @@
namespace Managing.Infrastructure.Databases.MongoDb.Configurations
{
public interface IManagingDatabaseSettings
{
string ConnectionString { get; set; }
string DatabaseName { get; set; }
}
}

View File

@@ -1,7 +0,0 @@
namespace Managing.Infrastructure.Databases.MongoDb.Configurations;
public class ManagingDatabaseSettings : IManagingDatabaseSettings
{
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
}

View File

@@ -1,130 +0,0 @@
using Managing.Infrastructure.Databases.MongoDb.Collections;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
namespace Managing.Infrastructure.Databases.MongoDb;
/// <summary>
/// Service responsible for creating and managing MongoDB indexes
/// </summary>
public class IndexService
{
private readonly IMongoDatabase _database;
private readonly ILogger<IndexService> _logger;
public IndexService(
IOptions<ManagingDatabaseSettings> databaseSettings,
ILogger<IndexService> logger)
{
var settings = databaseSettings.Value;
var client = new MongoClient(settings.ConnectionString);
_database = client.GetDatabase(settings.DatabaseName);
_logger = logger;
}
/// <summary>
/// Creates all necessary indexes for the application
/// </summary>
public async Task CreateIndexesAsync()
{
try
{
_logger.LogInformation("Creating MongoDB indexes...");
// Create indexes for BacktestDto
await CreateBacktestIndexesAsync();
await CreateBundleBacktestIndexesAsync();
// Create indexes for GeneticRequestDto
await CreateGeneticRequestIndexesAsync();
_logger.LogInformation("MongoDB indexes created successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to create MongoDB indexes");
throw;
}
}
/// <summary>
/// Creates indexes for the BacktestDto collection
/// </summary>
private async Task CreateBacktestIndexesAsync()
{
try
{
var collection = _database.GetCollection<BacktestDto>("Backtests");
// Create index on RequestId for faster queries
var requestIdIndexKeys = Builders<BacktestDto>.IndexKeys.Ascending(b => b.RequestId);
var requestIdIndexOptions = new CreateIndexOptions { Name = "RequestId_Index" };
var requestIdIndexModel = new CreateIndexModel<BacktestDto>(requestIdIndexKeys, requestIdIndexOptions);
// Create index (MongoDB will ignore if it already exists)
await collection.Indexes.CreateOneAsync(requestIdIndexModel);
_logger.LogInformation("Backtest RequestId index created successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to create Backtest indexes");
throw;
}
}
private async Task CreateBundleBacktestIndexesAsync()
{
var bundleCollection = _database.GetCollection<BundleBacktestRequestDto>("BundleBacktestRequests");
// Index on RequestId (unique)
var requestIdIndex = Builders<BundleBacktestRequestDto>.IndexKeys.Ascending(b => b.RequestId);
await bundleCollection.Indexes.CreateOneAsync(new CreateIndexModel<BundleBacktestRequestDto>(requestIdIndex, new CreateIndexOptions { Unique = true }));
// Index on User.Name (non-unique)
var userNameIndex = Builders<BundleBacktestRequestDto>.IndexKeys.Ascending("User.Name");
await bundleCollection.Indexes.CreateOneAsync(new CreateIndexModel<BundleBacktestRequestDto>(userNameIndex));
}
/// <summary>
/// Creates indexes for the GeneticRequestDto collection
/// </summary>
private async Task CreateGeneticRequestIndexesAsync()
{
try
{
var collection = _database.GetCollection<GeneticRequestDto>("GeneticRequests");
// Create index on RequestId for faster queries
var requestIdIndexKeys = Builders<GeneticRequestDto>.IndexKeys.Ascending(gr => gr.RequestId);
var requestIdIndexOptions = new CreateIndexOptions { Name = "RequestId_Index" };
var requestIdIndexModel = new CreateIndexModel<GeneticRequestDto>(requestIdIndexKeys, requestIdIndexOptions);
// Create index on User.Name for user-specific queries
var userIndexKeys = Builders<GeneticRequestDto>.IndexKeys.Ascending("User.Name");
var userIndexOptions = new CreateIndexOptions { Name = "User_Name_Index" };
var userIndexModel = new CreateIndexModel<GeneticRequestDto>(userIndexKeys, userIndexOptions);
// Create index on Status for filtering by status
var statusIndexKeys = Builders<GeneticRequestDto>.IndexKeys.Ascending(gr => gr.Status);
var statusIndexOptions = new CreateIndexOptions { Name = "Status_Index" };
var statusIndexModel = new CreateIndexModel<GeneticRequestDto>(statusIndexKeys, statusIndexOptions);
// Create indexes (MongoDB will ignore if they already exist)
await collection.Indexes.CreateManyAsync(new[]
{
requestIdIndexModel,
userIndexModel,
statusIndexModel
});
_logger.LogInformation("GeneticRequest indexes created successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to create GeneticRequest indexes");
throw;
}
}
}

View File

@@ -1,20 +0,0 @@
using MongoDB.Bson;
using MongoDB.Driver;
namespace Managing.Infrastructure.Databases.MongoDb
{
public static class MongoHelpers
{
public static async Task EnsureIndexExists(this IMongoDatabase database, string collectionName, string indexName)
{
var collection = database.GetCollection<BsonDocument>(collectionName);
var index = new BsonDocument
{
{indexName, 1}
};
var indexModel = new CreateIndexModel<BsonDocument>(index, new CreateIndexOptions { Unique = true });
await collection.Indexes.CreateOneAsync(indexModel).ConfigureAwait(false);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,183 +0,0 @@
using System.Linq.Expressions;
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using MongoDB.Bson;
using MongoDB.Driver;
namespace Managing.Infrastructure.Databases.MongoDb
{
public class MongoRepository<TDocument> : IMongoRepository<TDocument>
where TDocument : IDocument
{
private readonly IMongoCollection<TDocument> _collection;
private readonly IMongoDatabase _database;
public MongoRepository(IManagingDatabaseSettings settings)
{
_database = new MongoClient(settings.ConnectionString).GetDatabase(settings.DatabaseName);
_collection = _database.GetCollection<TDocument>(GetCollectionName(typeof(TDocument)));
}
private protected string GetCollectionName(Type documentType)
{
return ((BsonCollectionAttribute)documentType.GetCustomAttributes(
typeof(BsonCollectionAttribute),
true)
.FirstOrDefault())?.CollectionName;
}
public virtual IQueryable<TDocument> AsQueryable()
{
return _collection.AsQueryable();
}
public virtual IEnumerable<TDocument> FilterBy(
Expression<Func<TDocument, bool>> filterExpression)
{
return _collection.Find(filterExpression).ToEnumerable();
}
public virtual IEnumerable<TDocument> FilterBy(FilterDefinition<TDocument> filter)
{
return _collection.Find(filter).ToEnumerable();
}
public virtual IEnumerable<TDocument> FindAll()
{
return _collection.Find(_ => true).ToEnumerable();
}
public virtual IEnumerable<TProjected> FilterBy<TProjected>(
Expression<Func<TDocument, bool>> filterExpression,
Expression<Func<TDocument, TProjected>> projectionExpression)
{
return _collection.Find(filterExpression).Project(projectionExpression).ToEnumerable();
}
public virtual TDocument FindOne(Expression<Func<TDocument, bool>> filterExpression)
{
return _collection.Find(filterExpression).FirstOrDefault();
}
public virtual Task<TDocument> FindOneAsync(Expression<Func<TDocument, bool>> filterExpression)
{
return Task.Run(() => _collection.Find(filterExpression).FirstOrDefaultAsync());
}
public virtual TDocument FindById(string id)
{
var objectId = new ObjectId(id);
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, objectId);
return _collection.Find(filter).SingleOrDefault();
}
public virtual Task<TDocument> FindByIdAsync(string id)
{
return Task.Run(() =>
{
var objectId = new ObjectId(id);
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, objectId);
return _collection.Find(filter).SingleOrDefaultAsync();
});
}
public virtual void InsertOne(TDocument document)
{
_collection.InsertOne(document);
}
public virtual Task InsertOneAsync(TDocument document)
{
return Task.Run(() => _collection.InsertOneAsync(document));
}
public void InsertMany(ICollection<TDocument> documents)
{
_collection.InsertMany(documents);
}
public void DropCollection()
{
_database.DropCollection(GetCollectionName(typeof(TDocument)));
}
public virtual async Task InsertManyAsync(ICollection<TDocument> documents)
{
await _collection.InsertManyAsync(documents);
}
public void ReplaceOne(TDocument document)
{
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, document.Id);
_collection.FindOneAndReplace(filter, document);
}
public virtual async Task ReplaceOneAsync(TDocument document)
{
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, document.Id);
await _collection.FindOneAndReplaceAsync(filter, document);
}
public virtual void Update(TDocument entity)
{
if (entity.Id == ObjectId.Empty)
{
entity.Id = ObjectId.GenerateNewId();
}
var option = new ReplaceOptions { IsUpsert = true };
_collection.ReplaceOne(x => entity != null && x.Id == entity.Id, entity, option);
}
public virtual void DeleteOne(Expression<Func<TDocument, bool>> filterExpression)
{
_collection.FindOneAndDelete(filterExpression);
}
public virtual Task DeleteOneAsync(Expression<Func<TDocument, bool>> filterExpression)
{
return Task.Run(() => _collection.FindOneAndDeleteAsync(filterExpression));
}
public virtual void DeleteById(string id)
{
var objectId = new ObjectId(id);
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, objectId);
_collection.FindOneAndDelete(filter);
}
public virtual Task DeleteByIdAsync(string id)
{
return Task.Run(() =>
{
var objectId = new ObjectId(id);
var filter = Builders<TDocument>.Filter.Eq(doc => doc.Id, objectId);
_collection.FindOneAndDeleteAsync(filter);
});
}
public virtual void DeleteMany(Expression<Func<TDocument, bool>> filterExpression)
{
_collection.DeleteMany(filterExpression);
}
public virtual Task DeleteManyAsync(Expression<Func<TDocument, bool>> filterExpression)
{
return _collection.DeleteManyAsync(filterExpression);
}
public virtual void CreateIndex(string column)
{
var keys = Builders<TDocument>.IndexKeys.Ascending(column);
var indexOptions = new CreateIndexOptions { Unique = true };
var model = new CreateIndexModel<TDocument>(keys, indexOptions);
_collection.Indexes.CreateOne(model);
}
public IMongoCollection<TDocument> GetCollection()
{
return _collection;
}
}
}

View File

@@ -0,0 +1,11 @@
namespace Managing.Infrastructure.Databases.PostgreSql.Configurations;
public class PostgreSqlSettings : IPostgreSqlSettings
{
public string ConnectionString { get; set; } = string.Empty;
}
public interface IPostgreSqlSettings
{
string ConnectionString { get; set; }
}

View File

@@ -0,0 +1,18 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace Managing.Infrastructure.Databases.PostgreSql;
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ManagingDbContext>
{
public ManagingDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<ManagingDbContext>();
// Use a default connection string for design-time migrations
// This should match your local PostgreSQL setup
optionsBuilder.UseNpgsql("Host=localhost;Port=5432;Database=managing;Username=postgres;Password=postgres");
return new ManagingDbContext(optionsBuilder.Options);
}
}

View File

@@ -0,0 +1,20 @@
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
public class AccountEntity
{
public int Id { get; set; }
public string Name { get; set; }
public TradingExchanges Exchange { get; set; }
public AccountType Type { get; set; }
public string? Key { get; set; }
public string? Secret { get; set; }
public int? UserId { get; set; }
// Navigation properties
public UserEntity? User { get; set; }
// Store balances as JSON if needed, or skip them entirely
// public string? BalancesJson { get; set; }
}

View File

@@ -0,0 +1,83 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("Backtests")]
public class BacktestEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[MaxLength(255)]
public string Identifier { get; set; } = string.Empty;
[Required]
[MaxLength(255)]
public string RequestId { get; set; } = string.Empty;
[Required]
[Column(TypeName = "decimal(18,8)")]
public decimal FinalPnl { get; set; }
[Required]
public int WinRate { get; set; }
[Required]
[Column(TypeName = "decimal(18,8)")]
public decimal GrowthPercentage { get; set; }
[Required]
[Column(TypeName = "decimal(18,8)")]
public decimal HodlPercentage { get; set; }
[Required]
[Column(TypeName = "jsonb")]
public string ConfigJson { get; set; } = string.Empty;
[Required]
[Column(TypeName = "jsonb")]
public string PositionsJson { get; set; } = string.Empty;
[Required]
[Column(TypeName = "jsonb")]
public string SignalsJson { get; set; } = string.Empty;
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; }
[Required]
[Column(TypeName = "jsonb")]
public string MoneyManagementJson { get; set; } = string.Empty;
[Required]
[MaxLength(255)]
public string UserName { get; set; } = string.Empty;
[Column(TypeName = "jsonb")]
public string? StatisticsJson { get; set; }
[Required]
[Column(TypeName = "decimal(18,8)")]
public decimal Fees { get; set; }
[Required]
public double Score { get; set; }
[Column(TypeName = "text")]
public string ScoreMessage { get; set; } = string.Empty;
[Column(TypeName = "text")]
public string? Metadata { get; set; }
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[Required]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,36 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("BotBackups")]
public class BotBackupEntity
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(255)]
public string Identifier { get; set; }
[MaxLength(255)]
public string? UserName { get; set; }
public int? UserId { get; set; }
// Navigation properties
[ForeignKey("UserId")]
public UserEntity? User { get; set; }
/// <summary>
/// Bot configuration and state data stored as JSON string
/// </summary>
[Column(TypeName = "text")]
public string Data { get; set; }
public BotStatus LastStatus { get; set; }
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,70 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Managing.Domain.Backtests;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("BundleBacktestRequests")]
public class BundleBacktestRequestEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[MaxLength(255)]
public string RequestId { get; set; } = string.Empty;
[Required]
[MaxLength(255)]
public string UserName { get; set; } = string.Empty;
// Foreign key to User entity
public int? UserId { get; set; }
// Navigation property to User entity
public UserEntity? User { get; set; }
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? CompletedAt { get; set; }
[Required]
public BundleBacktestRequestStatus Status { get; set; }
[Required]
[Column(TypeName = "text")]
public string BacktestRequestsJson { get; set; } = string.Empty;
[Required]
public int TotalBacktests { get; set; }
[Required]
public int CompletedBacktests { get; set; }
[Required]
public int FailedBacktests { get; set; }
[Column(TypeName = "text")]
public string? ErrorMessage { get; set; }
[Column(TypeName = "text")]
public string? ProgressInfo { get; set; }
[MaxLength(500)]
public string? CurrentBacktest { get; set; }
public int? EstimatedTimeRemainingSeconds { get; set; }
[Required]
[MaxLength(255)]
public string Name { get; set; } = string.Empty;
[Required]
[Column(TypeName = "jsonb")]
public string ResultsJson { get; set; } = "[]";
[Required]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("FundingRates")]
public class FundingRateEntity
{
[Key]
public int Id { get; set; }
public Ticker Ticker { get; set; }
public TradingExchanges Exchange { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal Rate { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal OpenInterest { get; set; }
public DateTime Date { get; set; }
public TradeDirection Direction { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,38 @@
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
public class GeneticRequestEntity
{
public int Id { get; set; }
public string RequestId { get; set; }
public int? UserId { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? CompletedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public string Status { get; set; } // GeneticRequestStatus as string
public Ticker Ticker { get; set; }
public Timeframe Timeframe { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public decimal Balance { get; set; }
public int PopulationSize { get; set; }
public int Generations { get; set; }
public double MutationRate { get; set; }
public GeneticSelectionMethod SelectionMethod { get; set; }
public GeneticCrossoverMethod CrossoverMethod { get; set; }
public GeneticMutationMethod MutationMethod { get; set; }
public int ElitismPercentage { get; set; }
public double MaxTakeProfit { get; set; }
public string? EligibleIndicatorsJson { get; set; } // Store List<IndicatorType> as JSON
public double? BestFitness { get; set; }
public string? BestIndividual { get; set; }
public string? ErrorMessage { get; set; }
public string? ProgressInfo { get; set; }
public string? BestChromosome { get; set; }
public double? BestFitnessSoFar { get; set; }
public int CurrentGeneration { get; set; }
// Navigation properties
public UserEntity? User { get; set; }
}

View File

@@ -0,0 +1,39 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("Indicators")]
public class IndicatorEntity
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(255)]
public string Name { get; set; }
public IndicatorType Type { get; set; }
public Timeframe Timeframe { get; set; }
public SignalType SignalType { get; set; }
public int MinimumHistory { get; set; }
public int? Period { get; set; }
public int? FastPeriods { get; set; }
public int? SlowPeriods { get; set; }
public int? SignalPeriods { get; set; }
public double? Multiplier { get; set; }
public int? StochPeriods { get; set; }
public int? SmoothPeriods { get; set; }
public int? CyclePeriods { get; set; }
[MaxLength(255)]
public string? UserName { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
// Navigation property for the many-to-many relationship with scenarios
public virtual ICollection<ScenarioIndicatorEntity> ScenarioIndicators { get; set; } = new List<ScenarioIndicatorEntity>();
}

View File

@@ -0,0 +1,43 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("MoneyManagements")]
public class MoneyManagementEntity
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(255)]
public string Name { get; set; }
[Required]
public Timeframe Timeframe { get; set; }
[Required]
[Column(TypeName = "decimal(18,8)")]
public decimal StopLoss { get; set; }
[Required]
[Column(TypeName = "decimal(18,8)")]
public decimal TakeProfit { get; set; }
[Required]
[Column(TypeName = "decimal(18,8)")]
public decimal Leverage { get; set; }
[MaxLength(255)]
public string? UserName { get; set; }
public int? UserId { get; set; }
// Navigation properties
[ForeignKey("UserId")]
public UserEntity? User { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,61 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("Positions")]
public class PositionEntity
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(255)]
public string Identifier { get; set; }
public DateTime Date { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal ProfitAndLoss { get; set; }
public TradeDirection OriginDirection { get; set; }
public PositionStatus Status { get; set; }
public Ticker Ticker { get; set; }
public PositionInitiator Initiator { get; set; }
[MaxLength(255)]
public string SignalIdentifier { get; set; }
[MaxLength(255)]
public string AccountName { get; set; }
[MaxLength(255)]
public string? UserName { get; set; }
// Foreign keys to trades
public int? OpenTradeId { get; set; }
public int? StopLossTradeId { get; set; }
public int? TakeProfit1TradeId { get; set; }
public int? TakeProfit2TradeId { get; set; }
// Money management data stored as JSON
[Column(TypeName = "text")]
public string? MoneyManagementJson { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
[ForeignKey("OpenTradeId")]
public virtual TradeEntity? OpenTrade { get; set; }
[ForeignKey("StopLossTradeId")]
public virtual TradeEntity? StopLossTrade { get; set; }
[ForeignKey("TakeProfit1TradeId")]
public virtual TradeEntity? TakeProfit1Trade { get; set; }
[ForeignKey("TakeProfit2TradeId")]
public virtual TradeEntity? TakeProfit2Trade { get; set; }
}

View File

@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("Scenarios")]
public class ScenarioEntity
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(255)]
public string Name { get; set; }
public int LoopbackPeriod { get; set; }
[MaxLength(255)]
public string? UserName { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
// Navigation property for the many-to-many relationship with indicators
public virtual ICollection<ScenarioIndicatorEntity> ScenarioIndicators { get; set; } = new List<ScenarioIndicatorEntity>();
}

View File

@@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("ScenarioIndicators")]
public class ScenarioIndicatorEntity
{
[Key]
public int Id { get; set; }
public int ScenarioId { get; set; }
public int IndicatorId { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
[ForeignKey("ScenarioId")]
public virtual ScenarioEntity Scenario { get; set; } = null!;
[ForeignKey("IndicatorId")]
public virtual IndicatorEntity Indicator { get; set; } = null!;
}

View File

@@ -0,0 +1,38 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("Signals")]
public class SignalEntity
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(255)]
public string Identifier { get; set; }
public TradeDirection Direction { get; set; }
public Confidence Confidence { get; set; }
public DateTime Date { get; set; }
public Ticker Ticker { get; set; }
public SignalStatus Status { get; set; }
public Timeframe Timeframe { get; set; }
public IndicatorType Type { get; set; }
public SignalType SignalType { get; set; }
[MaxLength(255)]
public string IndicatorName { get; set; }
[MaxLength(255)]
public string? UserName { get; set; }
// Candle data stored as JSON
[Column(TypeName = "text")]
public string? CandleJson { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("SpotlightOverviews")]
public class SpotlightOverviewEntity
{
[Key]
public int Id { get; set; }
public Guid Identifier { get; set; }
public DateTime DateTime { get; set; }
public int ScenarioCount { get; set; }
/// <summary>
/// JSON column containing the complex nested spotlights data
/// This stores the List<SpotlightDto> as JSON to avoid complex normalization
/// </summary>
[Column(TypeName = "jsonb")]
public string SpotlightsJson { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,32 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
public class SynthMinersLeaderboardEntity
{
[Key]
public Guid Id { get; set; }
[Required]
[MaxLength(32)]
public string Asset { get; set; }
[Required]
public int TimeIncrement { get; set; }
public DateTime? SignalDate { get; set; }
[Required]
public bool IsBacktest { get; set; }
[Column(TypeName = "jsonb")]
public string MinersData { get; set; } // JSON serialized List<MinerInfo>
[Required]
[MaxLength(255)]
public string CacheKey { get; set; }
[Required]
public DateTime CreatedAt { get; set; }
}

View File

@@ -0,0 +1,38 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
public class SynthPredictionEntity
{
[Key]
public Guid Id { get; set; }
[Required]
[MaxLength(32)]
public string Asset { get; set; }
[Required]
public int MinerUid { get; set; }
[Required]
public int TimeIncrement { get; set; }
[Required]
public int TimeLength { get; set; }
public DateTime? SignalDate { get; set; }
[Required]
public bool IsBacktest { get; set; }
[Column(TypeName = "jsonb")]
public string PredictionData { get; set; } // JSON serialized MinerPrediction
[Required]
[MaxLength(255)]
public string CacheKey { get; set; }
[Required]
public DateTime CreatedAt { get; set; }
}

View File

@@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("TopVolumeTickers")]
public class TopVolumeTickerEntity
{
[Key]
public int Id { get; set; }
public Ticker Ticker { get; set; }
public DateTime Date { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal Volume { get; set; }
public int Rank { get; set; }
public TradingExchanges Exchange { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,39 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("Trades")]
public class TradeEntity
{
[Key]
public int Id { get; set; }
public DateTime Date { get; set; }
public TradeDirection Direction { get; set; }
public TradeStatus Status { get; set; }
public TradeType TradeType { get; set; }
public Ticker Ticker { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal Fee { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal Quantity { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal Price { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal Leverage { get; set; }
[MaxLength(255)]
public string? ExchangeOrderId { get; set; }
[Column(TypeName = "text")]
public string? Message { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,40 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("Traders")]
public class TraderEntity
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(255)]
public string Address { get; set; }
public int Winrate { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal Pnl { get; set; }
public int TradeCount { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal AverageWin { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal AverageLoss { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal Roi { get; set; }
/// <summary>
/// Indicates whether this is a best trader (true) or bad trader (false)
/// This allows us to use one table for both types
/// </summary>
public bool IsBestTrader { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,10 @@
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
public class UserEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string? AgentName { get; set; }
public string? AvatarUrl { get; set; }
public string? TelegramChannel { get; set; }
}

View File

@@ -0,0 +1,30 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("Workers")]
public class WorkerEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public WorkerType WorkerType { get; set; }
[Required]
public DateTime StartTime { get; set; }
public DateTime? LastRunTime { get; set; }
[Required]
public int ExecutionCount { get; set; }
[Required]
public long DelayTicks { get; set; } // TimeSpan is not supported, store as ticks
[Required]
public bool IsActive { get; set; }
}

View File

@@ -0,0 +1,521 @@
using Managing.Infrastructure.Databases.PostgreSql.Entities;
using Microsoft.EntityFrameworkCore;
namespace Managing.Infrastructure.Databases.PostgreSql;
public class ManagingDbContext : DbContext
{
public ManagingDbContext(DbContextOptions<ManagingDbContext> options) : base(options)
{
}
public DbSet<AccountEntity> Accounts { get; set; }
public DbSet<UserEntity> Users { get; set; }
public DbSet<GeneticRequestEntity> GeneticRequests { get; set; }
public DbSet<BacktestEntity> Backtests { get; set; }
public DbSet<BundleBacktestRequestEntity> BundleBacktestRequests { get; set; }
// Trading entities
public DbSet<ScenarioEntity> Scenarios { get; set; }
public DbSet<IndicatorEntity> Indicators { get; set; }
public DbSet<ScenarioIndicatorEntity> ScenarioIndicators { get; set; }
public DbSet<SignalEntity> Signals { get; set; }
public DbSet<PositionEntity> Positions { get; set; }
public DbSet<TradeEntity> Trades { get; set; }
// Statistics entities
public DbSet<TopVolumeTickerEntity> TopVolumeTickers { get; set; }
public DbSet<SpotlightOverviewEntity> SpotlightOverviews { get; set; }
public DbSet<TraderEntity> Traders { get; set; }
public DbSet<FundingRateEntity> FundingRates { get; set; }
// Bot entities
public DbSet<BotBackupEntity> BotBackups { get; set; }
// Settings entities
public DbSet<MoneyManagementEntity> MoneyManagements { get; set; }
// Worker entities
public DbSet<WorkerEntity> Workers { get; set; }
public DbSet<SynthMinersLeaderboardEntity> SynthMinersLeaderboards { get; set; }
public DbSet<SynthPredictionEntity> SynthPredictions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Configure Account entity
modelBuilder.Entity<AccountEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
entity.Property(e => e.Key).HasMaxLength(500);
entity.Property(e => e.Secret).HasMaxLength(500);
entity.Property(e => e.Exchange)
.IsRequired()
.HasConversion<string>(); // Store enum as string
entity.Property(e => e.Type)
.IsRequired()
.HasConversion<string>(); // Store enum as string
// Create unique index on account name
entity.HasIndex(e => e.Name).IsUnique();
entity.HasIndex(e => e.Key);
// Configure relationship with User
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.SetNull);
});
// Configure User entity
modelBuilder.Entity<UserEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
entity.Property(e => e.AgentName).HasMaxLength(255);
entity.Property(e => e.AvatarUrl).HasMaxLength(500);
entity.Property(e => e.TelegramChannel).HasMaxLength(255);
});
// Configure GeneticRequest entity
modelBuilder.Entity<GeneticRequestEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255);
entity.Property(e => e.Status).IsRequired().HasMaxLength(50);
entity.Property(e => e.Ticker)
.IsRequired()
.HasConversion<string>(); // Store enum as string
entity.Property(e => e.Timeframe)
.IsRequired()
.HasConversion<string>(); // Store enum as string
entity.Property(e => e.SelectionMethod)
.IsRequired()
.HasConversion<string>(); // Store enum as string
entity.Property(e => e.CrossoverMethod)
.IsRequired()
.HasConversion<string>(); // Store enum as string
entity.Property(e => e.MutationMethod)
.IsRequired()
.HasConversion<string>(); // Store enum as string
entity.Property(e => e.Balance).HasColumnType("decimal(18,8)");
entity.Property(e => e.BestIndividual).HasMaxLength(4000);
entity.Property(e => e.ErrorMessage).HasMaxLength(2000);
entity.Property(e => e.ProgressInfo).HasMaxLength(4000);
entity.Property(e => e.BestChromosome).HasMaxLength(4000);
entity.Property(e => e.EligibleIndicatorsJson).HasMaxLength(2000);
// Create indexes
entity.HasIndex(e => e.RequestId).IsUnique();
entity.HasIndex(e => e.Status);
entity.HasIndex(e => e.CreatedAt);
// Configure relationship with User
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.SetNull);
});
// Configure Backtest entity
modelBuilder.Entity<BacktestEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255);
entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255);
entity.Property(e => e.UserName).IsRequired().HasMaxLength(255);
entity.Property(e => e.FinalPnl).HasColumnType("decimal(18,8)");
entity.Property(e => e.GrowthPercentage).HasColumnType("decimal(18,8)");
entity.Property(e => e.HodlPercentage).HasColumnType("decimal(18,8)");
entity.Property(e => e.Fees).HasColumnType("decimal(18,8)");
entity.Property(e => e.ConfigJson).HasColumnType("jsonb");
entity.Property(e => e.PositionsJson).HasColumnType("jsonb");
entity.Property(e => e.SignalsJson).HasColumnType("jsonb");
entity.Property(e => e.MoneyManagementJson).HasColumnType("jsonb");
entity.Property(e => e.StatisticsJson).HasColumnType("jsonb");
entity.Property(e => e.ScoreMessage).HasMaxLength(1000);
entity.Property(e => e.Metadata).HasColumnType("text");
// Create indexes for common queries
entity.HasIndex(e => e.Identifier).IsUnique();
entity.HasIndex(e => e.RequestId);
entity.HasIndex(e => e.UserName);
entity.HasIndex(e => e.Score);
entity.HasIndex(e => e.FinalPnl);
entity.HasIndex(e => e.WinRate);
entity.HasIndex(e => e.StartDate);
entity.HasIndex(e => e.EndDate);
entity.HasIndex(e => e.CreatedAt);
// Composite indexes for efficient pagination and filtering
entity.HasIndex(e => new { e.UserName, e.Score });
entity.HasIndex(e => new { e.RequestId, e.Score });
});
// Configure BundleBacktestRequest entity
modelBuilder.Entity<BundleBacktestRequestEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255);
entity.Property(e => e.UserName).IsRequired().HasMaxLength(255);
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
entity.Property(e => e.Status)
.IsRequired()
.HasConversion<string>(); // Store enum as string
entity.Property(e => e.BacktestRequestsJson).HasColumnType("text");
entity.Property(e => e.ErrorMessage).HasColumnType("text");
entity.Property(e => e.ProgressInfo).HasColumnType("text");
entity.Property(e => e.CurrentBacktest).HasMaxLength(500);
entity.Property(e => e.ResultsJson).HasColumnType("jsonb");
// Configure relationship with User
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.SetNull);
// Create indexes for common queries
entity.HasIndex(e => e.RequestId).IsUnique();
entity.HasIndex(e => e.UserName);
entity.HasIndex(e => e.Status);
entity.HasIndex(e => e.CreatedAt);
entity.HasIndex(e => e.CompletedAt);
entity.HasIndex(e => e.UserId);
// Composite index for user queries ordered by creation date
entity.HasIndex(e => new { e.UserName, e.CreatedAt });
});
// Configure Scenario entity
modelBuilder.Entity<ScenarioEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
entity.Property(e => e.UserName).HasMaxLength(255);
// Create indexes
entity.HasIndex(e => e.Name);
entity.HasIndex(e => e.UserName);
entity.HasIndex(e => e.CreatedAt);
// Composite index for user scenarios
entity.HasIndex(e => new { e.UserName, e.Name });
});
// Configure Indicator entity
modelBuilder.Entity<IndicatorEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
entity.Property(e => e.Type).IsRequired().HasConversion<string>();
entity.Property(e => e.Timeframe).IsRequired().HasConversion<string>();
entity.Property(e => e.SignalType).IsRequired().HasConversion<string>();
entity.Property(e => e.UserName).HasMaxLength(255);
// Create indexes
entity.HasIndex(e => e.Name);
entity.HasIndex(e => e.Type);
entity.HasIndex(e => e.UserName);
entity.HasIndex(e => e.CreatedAt);
// Composite index for user indicators
entity.HasIndex(e => new { e.UserName, e.Name });
});
// Configure ScenarioIndicator junction table
modelBuilder.Entity<ScenarioIndicatorEntity>(entity =>
{
entity.HasKey(e => e.Id);
// Configure relationships
entity.HasOne(e => e.Scenario)
.WithMany(s => s.ScenarioIndicators)
.HasForeignKey(e => e.ScenarioId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(e => e.Indicator)
.WithMany(i => i.ScenarioIndicators)
.HasForeignKey(e => e.IndicatorId)
.OnDelete(DeleteBehavior.Cascade);
// Create indexes
entity.HasIndex(e => e.ScenarioId);
entity.HasIndex(e => e.IndicatorId);
entity.HasIndex(e => new { e.ScenarioId, e.IndicatorId }).IsUnique();
});
// Configure Signal entity
modelBuilder.Entity<SignalEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255);
entity.Property(e => e.Direction).IsRequired().HasConversion<string>();
entity.Property(e => e.Confidence).IsRequired().HasConversion<string>();
entity.Property(e => e.Ticker).IsRequired().HasConversion<string>();
entity.Property(e => e.Status).IsRequired().HasConversion<string>();
entity.Property(e => e.Timeframe).IsRequired().HasConversion<string>();
entity.Property(e => e.Type).IsRequired().HasConversion<string>();
entity.Property(e => e.SignalType).IsRequired().HasConversion<string>();
entity.Property(e => e.IndicatorName).IsRequired().HasMaxLength(255);
entity.Property(e => e.UserName).HasMaxLength(255);
entity.Property(e => e.CandleJson).HasColumnType("text");
// Create indexes
entity.HasIndex(e => e.Identifier);
entity.HasIndex(e => e.UserName);
entity.HasIndex(e => e.Date);
entity.HasIndex(e => e.Ticker);
entity.HasIndex(e => e.Status);
entity.HasIndex(e => e.CreatedAt);
// Composite indexes for common queries
entity.HasIndex(e => new { e.UserName, e.Date });
entity.HasIndex(e => new { e.Identifier, e.Date, e.UserName }).IsUnique();
});
// Configure Position entity
modelBuilder.Entity<PositionEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255);
entity.Property(e => e.ProfitAndLoss).HasColumnType("decimal(18,8)");
entity.Property(e => e.OriginDirection).IsRequired().HasConversion<string>();
entity.Property(e => e.Status).IsRequired().HasConversion<string>();
entity.Property(e => e.Ticker).IsRequired().HasConversion<string>();
entity.Property(e => e.Initiator).IsRequired().HasConversion<string>();
entity.Property(e => e.SignalIdentifier).IsRequired().HasMaxLength(255);
entity.Property(e => e.AccountName).IsRequired().HasMaxLength(255);
entity.Property(e => e.UserName).HasMaxLength(255);
entity.Property(e => e.MoneyManagementJson).HasColumnType("text");
// Configure relationships with trades
entity.HasOne(e => e.OpenTrade)
.WithMany()
.HasForeignKey(e => e.OpenTradeId)
.OnDelete(DeleteBehavior.SetNull);
entity.HasOne(e => e.StopLossTrade)
.WithMany()
.HasForeignKey(e => e.StopLossTradeId)
.OnDelete(DeleteBehavior.SetNull);
entity.HasOne(e => e.TakeProfit1Trade)
.WithMany()
.HasForeignKey(e => e.TakeProfit1TradeId)
.OnDelete(DeleteBehavior.SetNull);
entity.HasOne(e => e.TakeProfit2Trade)
.WithMany()
.HasForeignKey(e => e.TakeProfit2TradeId)
.OnDelete(DeleteBehavior.SetNull);
// Create indexes
entity.HasIndex(e => e.Identifier).IsUnique();
entity.HasIndex(e => e.UserName);
entity.HasIndex(e => e.Status);
entity.HasIndex(e => e.Initiator);
entity.HasIndex(e => e.Date);
entity.HasIndex(e => e.CreatedAt);
// Composite indexes
entity.HasIndex(e => new { e.UserName, e.Identifier });
});
// Configure Trade entity
modelBuilder.Entity<TradeEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Direction).IsRequired().HasConversion<string>();
entity.Property(e => e.Status).IsRequired().HasConversion<string>();
entity.Property(e => e.TradeType).IsRequired().HasConversion<string>();
entity.Property(e => e.Ticker).IsRequired().HasConversion<string>();
entity.Property(e => e.Fee).HasColumnType("decimal(18,8)");
entity.Property(e => e.Quantity).HasColumnType("decimal(18,8)");
entity.Property(e => e.Price).HasColumnType("decimal(18,8)");
entity.Property(e => e.Leverage).HasColumnType("decimal(18,8)");
entity.Property(e => e.ExchangeOrderId).HasMaxLength(255);
entity.Property(e => e.Message).HasColumnType("text");
// Create indexes
entity.HasIndex(e => e.Date);
entity.HasIndex(e => e.Status);
entity.HasIndex(e => e.ExchangeOrderId);
entity.HasIndex(e => e.CreatedAt);
});
// Configure TopVolumeTicker entity
modelBuilder.Entity<TopVolumeTickerEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Volume).HasPrecision(18, 8);
// Create indexes
entity.HasIndex(e => e.Ticker);
entity.HasIndex(e => e.Date);
entity.HasIndex(e => e.Exchange);
entity.HasIndex(e => e.Rank);
// Composite indexes for efficient queries
entity.HasIndex(e => new { e.Exchange, e.Date });
entity.HasIndex(e => new { e.Date, e.Rank });
});
// Configure SpotlightOverview entity
modelBuilder.Entity<SpotlightOverviewEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Identifier).IsRequired();
entity.Property(e => e.SpotlightsJson).HasColumnType("jsonb");
// Create indexes
entity.HasIndex(e => e.Identifier).IsUnique();
entity.HasIndex(e => e.DateTime);
entity.HasIndex(e => e.ScenarioCount);
// Composite index for efficient queries
entity.HasIndex(e => new { e.DateTime, e.ScenarioCount });
});
// Configure Trader entity
modelBuilder.Entity<TraderEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Address).IsRequired().HasMaxLength(255);
entity.Property(e => e.Pnl).HasPrecision(18, 8);
entity.Property(e => e.AverageWin).HasPrecision(18, 8);
entity.Property(e => e.AverageLoss).HasPrecision(18, 8);
entity.Property(e => e.Roi).HasPrecision(18, 8);
// Create indexes
entity.HasIndex(e => e.Address);
entity.HasIndex(e => e.IsBestTrader);
entity.HasIndex(e => e.Winrate);
entity.HasIndex(e => e.Pnl);
entity.HasIndex(e => e.Roi);
// Composite indexes for efficient queries
entity.HasIndex(e => new { e.IsBestTrader, e.Winrate });
entity.HasIndex(e => new { e.IsBestTrader, e.Roi });
entity.HasIndex(e => new { e.Address, e.IsBestTrader }).IsUnique();
});
// Configure FundingRate entity
modelBuilder.Entity<FundingRateEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Rate).HasPrecision(18, 8);
entity.Property(e => e.OpenInterest).HasPrecision(18, 8);
// Create indexes
entity.HasIndex(e => e.Ticker);
entity.HasIndex(e => e.Exchange);
entity.HasIndex(e => e.Date);
entity.HasIndex(e => e.Direction);
// Composite indexes for efficient queries
entity.HasIndex(e => new { e.Ticker, e.Exchange });
entity.HasIndex(e => new { e.Exchange, e.Date });
entity.HasIndex(e => new { e.Ticker, e.Exchange, e.Date }).IsUnique();
});
// Configure BotBackup entity
modelBuilder.Entity<BotBackupEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255);
entity.Property(e => e.UserName).HasMaxLength(255);
entity.Property(e => e.Data).IsRequired().HasColumnType("text");
// Create indexes
entity.HasIndex(e => e.Identifier).IsUnique();
entity.HasIndex(e => e.UserName);
entity.HasIndex(e => e.LastStatus);
entity.HasIndex(e => e.CreateDate);
// Composite index for user bots
entity.HasIndex(e => new { e.UserName, e.CreateDate });
// Configure relationship with User
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.SetNull);
});
// Configure MoneyManagement entity
modelBuilder.Entity<MoneyManagementEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
entity.Property(e => e.Timeframe).IsRequired().HasConversion<string>();
entity.Property(e => e.StopLoss).HasColumnType("decimal(18,8)");
entity.Property(e => e.TakeProfit).HasColumnType("decimal(18,8)");
entity.Property(e => e.Leverage).HasColumnType("decimal(18,8)");
entity.Property(e => e.UserName).HasMaxLength(255);
// Create indexes
entity.HasIndex(e => e.Name);
entity.HasIndex(e => e.UserName);
// Composite index for user money managements
entity.HasIndex(e => new { e.UserName, e.Name });
// Configure relationship with User
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.SetNull);
});
// Configure Worker entity
modelBuilder.Entity<WorkerEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.WorkerType).IsRequired().HasConversion<string>();
entity.Property(e => e.StartTime).IsRequired();
entity.Property(e => e.LastRunTime);
entity.Property(e => e.ExecutionCount).IsRequired();
entity.Property(e => e.DelayTicks).IsRequired();
entity.Property(e => e.IsActive).IsRequired();
entity.HasIndex(e => e.WorkerType).IsUnique();
});
// Configure SynthMinersLeaderboard entity
modelBuilder.Entity<SynthMinersLeaderboardEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Asset).IsRequired().HasMaxLength(32);
entity.Property(e => e.TimeIncrement).IsRequired();
entity.Property(e => e.SignalDate);
entity.Property(e => e.IsBacktest).IsRequired();
entity.Property(e => e.MinersData).HasColumnType("jsonb");
entity.Property(e => e.CacheKey).IsRequired().HasMaxLength(255);
entity.Property(e => e.CreatedAt).IsRequired();
entity.HasIndex(e => e.CacheKey).IsUnique();
entity.HasIndex(e => e.CreatedAt);
});
// Configure SynthPrediction entity
modelBuilder.Entity<SynthPredictionEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Asset).IsRequired().HasMaxLength(32);
entity.Property(e => e.MinerUid).IsRequired();
entity.Property(e => e.TimeIncrement).IsRequired();
entity.Property(e => e.TimeLength).IsRequired();
entity.Property(e => e.SignalDate);
entity.Property(e => e.IsBacktest).IsRequired();
entity.Property(e => e.PredictionData).HasColumnType("jsonb");
entity.Property(e => e.CacheKey).IsRequired().HasMaxLength(255);
entity.Property(e => e.CreatedAt).IsRequired();
entity.HasIndex(e => e.CacheKey).IsUnique();
entity.HasIndex(e => e.CreatedAt);
});
}
}

View File

@@ -0,0 +1,159 @@
using System.Data;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts;
using Microsoft.EntityFrameworkCore;
namespace Managing.Infrastructure.Databases.PostgreSql;
public class PostgreSqlAccountRepository : IAccountRepository
{
private readonly ManagingDbContext _context;
private readonly ICacheService _cacheService;
public PostgreSqlAccountRepository(ManagingDbContext context, ICacheService cacheService)
{
_context = context;
_cacheService = cacheService;
}
/// <summary>
/// Ensures the database connection is open before executing queries
/// </summary>
private async Task EnsureConnectionOpenAsync()
{
if (_context.Database.GetDbConnection().State != ConnectionState.Open)
{
await _context.Database.OpenConnectionAsync();
}
}
/// <summary>
/// Safely closes the database connection if it was opened by us
/// </summary>
private async Task SafeCloseConnectionAsync()
{
if (_context.Database.GetDbConnection().State == ConnectionState.Open)
{
await _context.Database.CloseConnectionAsync();
}
}
public void DeleteAccountByName(string name)
{
var accountEntity = _context.Accounts
.AsTracking() // Explicitly enable tracking for delete operations
.FirstOrDefault(a => a.Name == name);
if (accountEntity != null)
{
_context.Accounts.Remove(accountEntity);
_context.SaveChanges();
}
}
public async Task<Account> GetAccountByKeyAsync(string key)
{
try
{
await EnsureConnectionOpenAsync();
var accountEntity = await _context.Accounts
.AsNoTracking()
.Include(a => a.User)
.FirstOrDefaultAsync(a => a.Key == key)
.ConfigureAwait(false);
return PostgreSqlMappers.Map(accountEntity);
}
catch (Exception)
{
// If there's an error, try to reset the connection
await SafeCloseConnectionAsync();
throw;
}
}
public async Task<Account> GetAccountByNameAsync(string name)
{
try
{
var cacheKey = $"account_{name}";
var cachedAccount = _cacheService.GetValue<Account>(cacheKey);
if (cachedAccount != null)
{
return cachedAccount;
}
var accountEntity = await _context.Accounts
.AsNoTracking()
.Include(a => a.User)
.FirstOrDefaultAsync(a => a.Name == name)
.ConfigureAwait(false);
var account = PostgreSqlMappers.Map(accountEntity);
_cacheService.SaveValue(cacheKey, account, TimeSpan.FromHours(1));
return account;
}
catch (Exception ex)
{
// If there's an error, try to reset the connection
throw;
}
finally
{
}
}
public async Task<IEnumerable<Account>> GetAccountsAsync()
{
try
{
await EnsureConnectionOpenAsync();
// Use proper async operations with AsNoTracking for optimal performance
var accountEntities = await _context.Accounts
.AsNoTracking()
.Include(a => a.User)
.ToListAsync()
.ConfigureAwait(false);
return PostgreSqlMappers.Map(accountEntities);
}
catch (Exception)
{
// If there's an error, try to reset the connection
await SafeCloseConnectionAsync();
throw;
}
}
public async Task InsertAccountAsync(Account account)
{
var accountEntity = PostgreSqlMappers.Map(account);
// Handle User relationship - check if user exists or create new one
if (account.User != null)
{
var existingUser = await _context.Users
.AsTracking() // Explicitly enable tracking for this operation
.FirstOrDefaultAsync(u => u.Name == account.User.Name)
.ConfigureAwait(false);
if (existingUser != null)
{
accountEntity.UserId = existingUser.Id;
accountEntity.User = null; // Prevent EF from trying to insert duplicate user
}
else
{
// Let EF handle the new user creation
accountEntity.UserId = null;
}
}
// Balances are not stored in PostgreSQL, they remain in domain logic only
_context.Accounts.Add(accountEntity);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,730 @@
using System.Diagnostics;
using Exilion.TradingAtomics;
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.Users;
using Managing.Infrastructure.Databases.PostgreSql.Entities;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
namespace Managing.Infrastructure.Databases.PostgreSql;
public class PostgreSqlBacktestRepository : IBacktestRepository
{
private readonly ManagingDbContext _context;
public PostgreSqlBacktestRepository(ManagingDbContext context)
{
_context = context;
}
// User-specific operations
public void InsertBacktestForUser(User user, Backtest result)
{
ValidateBacktestData(result);
result.User = user;
var entity = PostgreSqlMappers.Map(result);
_context.Backtests.Add(entity);
_context.SaveChanges();
}
/// <summary>
/// Validates that all numeric fields in the backtest are of the correct type
/// </summary>
private void ValidateBacktestData(Backtest backtest)
{
// Ensure FinalPnl is a valid decimal
if (backtest.FinalPnl.GetType() != typeof(decimal))
{
throw new InvalidOperationException(
$"FinalPnl must be of type decimal, but got {backtest.FinalPnl.GetType().Name}");
}
// Ensure other numeric fields are correct
if (backtest.GrowthPercentage.GetType() != typeof(decimal))
{
throw new InvalidOperationException(
$"GrowthPercentage must be of type decimal, but got {backtest.GrowthPercentage.GetType().Name}");
}
if (backtest.HodlPercentage.GetType() != typeof(decimal))
{
throw new InvalidOperationException(
$"HodlPercentage must be of type decimal, but got {backtest.HodlPercentage.GetType().Name}");
}
if (backtest.Score.GetType() != typeof(double))
{
throw new InvalidOperationException(
$"Score must be of type double, but got {backtest.Score.GetType().Name}");
}
if (backtest.WinRate.GetType() != typeof(int))
{
throw new InvalidOperationException(
$"WinRate must be of type int, but got {backtest.WinRate.GetType().Name}");
}
}
public IEnumerable<Backtest> GetBacktestsByUser(User user)
{
var entities = _context.Backtests
.AsNoTracking()
.Where(b => b.UserName == user.Name)
.ToList();
return entities.Select(PostgreSqlMappers.Map);
}
public async Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user)
{
var entities = await _context.Backtests
.AsNoTracking()
.Where(b => b.UserName == user.Name)
.ToListAsync()
.ConfigureAwait(false);
return entities.Select(PostgreSqlMappers.Map);
}
public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
{
var entities = _context.Backtests
.AsNoTracking()
.Where(b => b.RequestId == requestId)
.ToList();
return entities.Select(PostgreSqlMappers.Map);
}
public async Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId)
{
var entities = await _context.Backtests
.AsNoTracking()
.Where(b => b.RequestId == requestId)
.ToListAsync()
.ConfigureAwait(false);
return entities.Select(PostgreSqlMappers.Map);
}
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId,
int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var stopwatch = Stopwatch.StartNew();
var baseQuery = _context.Backtests
.AsNoTracking()
.Where(b => b.RequestId == requestId);
var afterQueryMs = stopwatch.ElapsedMilliseconds;
var totalCount = baseQuery.Count();
var afterCountMs = stopwatch.ElapsedMilliseconds;
// Apply sorting
IQueryable<BacktestEntity> sortedQuery = sortBy.ToLower() switch
{
"score" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.Score)
: baseQuery.OrderBy(b => b.Score),
"finalpnl" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.FinalPnl)
: baseQuery.OrderBy(b => b.FinalPnl),
"winrate" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.WinRate)
: baseQuery.OrderBy(b => b.WinRate),
"growthpercentage" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.GrowthPercentage)
: baseQuery.OrderBy(b => b.GrowthPercentage),
"hodlpercentage" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.HodlPercentage)
: baseQuery.OrderBy(b => b.HodlPercentage),
_ => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.Score)
: baseQuery.OrderBy(b => b.Score)
};
var afterSortMs = stopwatch.ElapsedMilliseconds;
var entities = sortedQuery
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList();
var afterToListMs = stopwatch.ElapsedMilliseconds;
Console.WriteLine(
$"[PostgreSqlBacktestRepo] Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Sort: {afterSortMs - afterCountMs}ms, ToList: {afterToListMs - afterSortMs}ms, Total: {afterToListMs}ms");
var mappedBacktests = entities.Select(entity => new LightBacktest
{
Id = entity.Identifier,
Config = JsonConvert.DeserializeObject<TradingBotConfig>(entity.ConfigJson),
FinalPnl = entity.FinalPnl,
WinRate = entity.WinRate,
GrowthPercentage = entity.GrowthPercentage,
HodlPercentage = entity.HodlPercentage,
StartDate = entity.StartDate,
EndDate = entity.EndDate,
MaxDrawdown = !string.IsNullOrEmpty(entity.StatisticsJson)
? JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson)?.MaxDrawdown
: null,
Fees = entity.Fees,
SharpeRatio = !string.IsNullOrEmpty(entity.StatisticsJson)
? JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson)?.SharpeRatio != null
? (double?)JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson).SharpeRatio
: null
: null,
Score = entity.Score,
ScoreMessage = entity.ScoreMessage ?? string.Empty
});
return (mappedBacktests, totalCount);
}
public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(
string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var stopwatch = Stopwatch.StartNew();
var baseQuery = _context.Backtests
.AsNoTracking()
.Where(b => b.RequestId == requestId);
var afterQueryMs = stopwatch.ElapsedMilliseconds;
var totalCount = await baseQuery.CountAsync().ConfigureAwait(false);
var afterCountMs = stopwatch.ElapsedMilliseconds;
// Apply sorting
IQueryable<BacktestEntity> sortedQuery = sortBy.ToLower() switch
{
"score" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.Score)
: baseQuery.OrderBy(b => b.Score),
"finalpnl" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.FinalPnl)
: baseQuery.OrderBy(b => b.FinalPnl),
"winrate" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.WinRate)
: baseQuery.OrderBy(b => b.WinRate),
"growthpercentage" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.GrowthPercentage)
: baseQuery.OrderBy(b => b.GrowthPercentage),
"hodlpercentage" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.HodlPercentage)
: baseQuery.OrderBy(b => b.HodlPercentage),
_ => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.Score)
: baseQuery.OrderBy(b => b.Score)
};
var afterSortMs = stopwatch.ElapsedMilliseconds;
var entities = await sortedQuery
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync()
.ConfigureAwait(false);
var afterToListMs = stopwatch.ElapsedMilliseconds;
Console.WriteLine(
$"[PostgreSqlBacktestRepo] Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Sort: {afterSortMs - afterCountMs}ms, ToList: {afterToListMs - afterSortMs}ms, Total: {afterToListMs}ms");
var mappedBacktests = entities.Select(entity => new LightBacktest
{
Id = entity.Identifier,
Config = JsonConvert.DeserializeObject<TradingBotConfig>(entity.ConfigJson),
FinalPnl = entity.FinalPnl,
WinRate = entity.WinRate,
GrowthPercentage = entity.GrowthPercentage,
HodlPercentage = entity.HodlPercentage,
StartDate = entity.StartDate,
EndDate = entity.EndDate,
MaxDrawdown = !string.IsNullOrEmpty(entity.StatisticsJson)
? JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson)?.MaxDrawdown
: null,
Fees = entity.Fees,
SharpeRatio = !string.IsNullOrEmpty(entity.StatisticsJson)
? JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson)?.SharpeRatio != null
? (double?)JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson).SharpeRatio
: null
: null,
Score = entity.Score,
ScoreMessage = entity.ScoreMessage ?? string.Empty
});
return (mappedBacktests, totalCount);
}
public Backtest GetBacktestByIdForUser(User user, string id)
{
var entity = _context.Backtests
.AsNoTracking()
.FirstOrDefault(b => b.Identifier == id && b.UserName == user.Name);
return entity != null ? PostgreSqlMappers.Map(entity) : null;
}
public async Task<Backtest> GetBacktestByIdForUserAsync(User user, string id)
{
var entity = await _context.Backtests
.AsNoTracking()
.FirstOrDefaultAsync(b => b.Identifier == id && b.UserName == user.Name)
.ConfigureAwait(false);
return entity != null ? PostgreSqlMappers.Map(entity) : null;
}
public void DeleteBacktestByIdForUser(User user, string id)
{
var entity = _context.Backtests
.AsTracking()
.FirstOrDefault(b => b.Identifier == id && b.UserName == user.Name);
if (entity != null)
{
_context.Backtests.Remove(entity);
_context.SaveChanges();
}
}
public async Task DeleteBacktestByIdForUserAsync(User user, string id)
{
var entity = await _context.Backtests
.AsTracking()
.FirstOrDefaultAsync(b => b.Identifier == id && b.UserName == user.Name)
.ConfigureAwait(false);
if (entity != null)
{
_context.Backtests.Remove(entity);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
}
public void DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids)
{
var entities = _context.Backtests
.AsTracking()
.Where(b => b.UserName == user.Name && ids.Contains(b.Identifier))
.ToList();
if (entities.Any())
{
_context.Backtests.RemoveRange(entities);
_context.SaveChanges();
}
}
public async Task DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids)
{
var entities = await _context.Backtests
.AsTracking()
.Where(b => b.UserName == user.Name && ids.Contains(b.Identifier))
.ToListAsync()
.ConfigureAwait(false);
if (entities.Any())
{
_context.Backtests.RemoveRange(entities);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
}
public void DeleteAllBacktestsForUser(User user)
{
var entities = _context.Backtests
.AsTracking()
.Where(b => b.UserName == user.Name)
.ToList();
if (entities.Any())
{
_context.Backtests.RemoveRange(entities);
_context.SaveChanges();
}
}
public void DeleteBacktestsByRequestId(string requestId)
{
var entities = _context.Backtests
.AsTracking()
.Where(b => b.RequestId == requestId)
.ToList();
if (entities.Any())
{
_context.Backtests.RemoveRange(entities);
_context.SaveChanges();
}
}
public async Task DeleteBacktestsByRequestIdAsync(string requestId)
{
var entities = await _context.Backtests
.AsTracking()
.Where(b => b.RequestId == requestId)
.ToListAsync()
.ConfigureAwait(false);
if (entities.Any())
{
_context.Backtests.RemoveRange(entities);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
}
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page,
int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var stopwatch = Stopwatch.StartNew();
var baseQuery = _context.Backtests
.AsNoTracking()
.Where(b => b.UserName == user.Name);
var afterQueryMs = stopwatch.ElapsedMilliseconds;
var totalCount = baseQuery.Count();
var afterCountMs = stopwatch.ElapsedMilliseconds;
// Apply sorting
IQueryable<BacktestEntity> sortedQuery = sortBy.ToLower() switch
{
"score" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.Score)
: baseQuery.OrderBy(b => b.Score),
"finalpnl" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.FinalPnl)
: baseQuery.OrderBy(b => b.FinalPnl),
"winrate" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.WinRate)
: baseQuery.OrderBy(b => b.WinRate),
"growthpercentage" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.GrowthPercentage)
: baseQuery.OrderBy(b => b.GrowthPercentage),
"hodlpercentage" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.HodlPercentage)
: baseQuery.OrderBy(b => b.HodlPercentage),
_ => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.Score)
: baseQuery.OrderBy(b => b.Score)
};
var afterSortMs = stopwatch.ElapsedMilliseconds;
var entities = sortedQuery
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList();
var afterToListMs = stopwatch.ElapsedMilliseconds;
Console.WriteLine(
$"[PostgreSqlBacktestRepo] User Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Sort: {afterSortMs - afterCountMs}ms, ToList: {afterToListMs - afterSortMs}ms, Total: {afterToListMs}ms");
var mappedBacktests = entities.Select(entity => new LightBacktest
{
Id = entity.Identifier,
Config = JsonConvert.DeserializeObject<TradingBotConfig>(entity.ConfigJson),
FinalPnl = entity.FinalPnl,
WinRate = entity.WinRate,
GrowthPercentage = entity.GrowthPercentage,
HodlPercentage = entity.HodlPercentage,
StartDate = entity.StartDate,
EndDate = entity.EndDate,
MaxDrawdown = !string.IsNullOrEmpty(entity.StatisticsJson)
? JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson)?.MaxDrawdown
: null,
Fees = entity.Fees,
SharpeRatio = !string.IsNullOrEmpty(entity.StatisticsJson)
? JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson)?.SharpeRatio != null
? (double?)JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson).SharpeRatio
: null
: null,
Score = entity.Score,
ScoreMessage = entity.ScoreMessage ?? string.Empty
});
return (mappedBacktests, totalCount);
}
public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(
User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
{
var stopwatch = Stopwatch.StartNew();
var baseQuery = _context.Backtests
.AsNoTracking()
.Where(b => b.UserName == user.Name);
var afterQueryMs = stopwatch.ElapsedMilliseconds;
var totalCount = await baseQuery.CountAsync().ConfigureAwait(false);
var afterCountMs = stopwatch.ElapsedMilliseconds;
// Apply sorting
IQueryable<BacktestEntity> sortedQuery = sortBy.ToLower() switch
{
"score" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.Score)
: baseQuery.OrderBy(b => b.Score),
"finalpnl" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.FinalPnl)
: baseQuery.OrderBy(b => b.FinalPnl),
"winrate" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.WinRate)
: baseQuery.OrderBy(b => b.WinRate),
"growthpercentage" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.GrowthPercentage)
: baseQuery.OrderBy(b => b.GrowthPercentage),
"hodlpercentage" => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.HodlPercentage)
: baseQuery.OrderBy(b => b.HodlPercentage),
_ => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.Score)
: baseQuery.OrderBy(b => b.Score)
};
var afterSortMs = stopwatch.ElapsedMilliseconds;
var entities = await sortedQuery
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync()
.ConfigureAwait(false);
var afterToListMs = stopwatch.ElapsedMilliseconds;
Console.WriteLine(
$"[PostgreSqlBacktestRepo] User Query: {afterQueryMs}ms, Count: {afterCountMs - afterQueryMs}ms, Sort: {afterSortMs - afterCountMs}ms, ToList: {afterToListMs - afterSortMs}ms, Total: {afterToListMs}ms");
var mappedBacktests = entities.Select(entity => new LightBacktest
{
Id = entity.Identifier,
Config = JsonConvert.DeserializeObject<TradingBotConfig>(entity.ConfigJson),
FinalPnl = entity.FinalPnl,
WinRate = entity.WinRate,
GrowthPercentage = entity.GrowthPercentage,
HodlPercentage = entity.HodlPercentage,
StartDate = entity.StartDate,
EndDate = entity.EndDate,
MaxDrawdown = !string.IsNullOrEmpty(entity.StatisticsJson)
? JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson)?.MaxDrawdown
: null,
Fees = entity.Fees,
SharpeRatio = !string.IsNullOrEmpty(entity.StatisticsJson)
? JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson)?.SharpeRatio != null
? (double?)JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson).SharpeRatio
: null
: null,
Score = entity.Score,
ScoreMessage = entity.ScoreMessage ?? string.Empty
});
return (mappedBacktests, totalCount);
}
// Bundle backtest methods
public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
{
bundleRequest.User = user;
var entity = PostgreSqlMappers.Map(bundleRequest);
// Set the UserId by finding the user entity
var userEntity = _context.Users.FirstOrDefault(u => u.Name == user.Name);
if (userEntity != null)
{
entity.UserId = userEntity.Id;
}
_context.BundleBacktestRequests.Add(entity);
_context.SaveChanges();
}
public async Task InsertBundleBacktestRequestForUserAsync(User user, BundleBacktestRequest bundleRequest)
{
bundleRequest.User = user;
var entity = PostgreSqlMappers.Map(bundleRequest);
// Set the UserId by finding the user entity
var userEntity = await _context.Users.FirstOrDefaultAsync(u => u.Name == user.Name);
if (userEntity != null)
{
entity.UserId = userEntity.Id;
}
await _context.BundleBacktestRequests.AddAsync(entity);
await _context.SaveChangesAsync();
}
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user)
{
var entities = _context.BundleBacktestRequests
.AsNoTracking()
.Include(b => b.User)
.Where(b => b.UserName == user.Name)
.OrderByDescending(b => b.CreatedAt)
.ToList();
return entities.Select(PostgreSqlMappers.Map);
}
public async Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user)
{
var entities = await _context.BundleBacktestRequests
.AsNoTracking()
.Include(b => b.User)
.Where(b => b.UserName == user.Name)
.OrderByDescending(b => b.CreatedAt)
.ToListAsync()
.ConfigureAwait(false);
return entities.Select(PostgreSqlMappers.Map);
}
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id)
{
var entity = _context.BundleBacktestRequests
.AsNoTracking()
.Include(b => b.User)
.FirstOrDefault(b => b.RequestId == id && b.UserName == user.Name);
return entity != null ? PostgreSqlMappers.Map(entity) : null;
}
public async Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id)
{
var entity = await _context.BundleBacktestRequests
.AsNoTracking()
.Include(b => b.User)
.FirstOrDefaultAsync(b => b.RequestId == id && b.UserName == user.Name)
.ConfigureAwait(false);
return entity != null ? PostgreSqlMappers.Map(entity) : null;
}
public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest)
{
var entity = _context.BundleBacktestRequests
.AsTracking()
.FirstOrDefault(b => b.RequestId == bundleRequest.RequestId);
if (entity != null)
{
// Update the entity properties
entity.Status = bundleRequest.Status;
entity.CompletedAt = bundleRequest.CompletedAt;
entity.CompletedBacktests = bundleRequest.CompletedBacktests;
entity.FailedBacktests = bundleRequest.FailedBacktests;
entity.ErrorMessage = bundleRequest.ErrorMessage;
entity.ProgressInfo = bundleRequest.ProgressInfo;
entity.CurrentBacktest = bundleRequest.CurrentBacktest;
entity.EstimatedTimeRemainingSeconds = bundleRequest.EstimatedTimeRemainingSeconds;
entity.UpdatedAt = DateTime.UtcNow;
// Serialize Results to JSON
if (bundleRequest.Results != null && bundleRequest.Results.Any())
{
try
{
entity.ResultsJson = JsonConvert.SerializeObject(bundleRequest.Results);
}
catch
{
entity.ResultsJson = "[]";
}
}
else
{
entity.ResultsJson = "[]";
}
_context.SaveChanges();
}
}
public async Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest)
{
var entity = await _context.BundleBacktestRequests
.AsTracking()
.FirstOrDefaultAsync(b => b.RequestId == bundleRequest.RequestId)
.ConfigureAwait(false);
if (entity != null)
{
// Update the entity properties
entity.Status = bundleRequest.Status;
entity.CompletedAt = bundleRequest.CompletedAt;
entity.CompletedBacktests = bundleRequest.CompletedBacktests;
entity.FailedBacktests = bundleRequest.FailedBacktests;
entity.ErrorMessage = bundleRequest.ErrorMessage;
entity.ProgressInfo = bundleRequest.ProgressInfo;
entity.CurrentBacktest = bundleRequest.CurrentBacktest;
entity.EstimatedTimeRemainingSeconds = bundleRequest.EstimatedTimeRemainingSeconds;
entity.UpdatedAt = DateTime.UtcNow;
// Serialize Results to JSON
if (bundleRequest.Results != null && bundleRequest.Results.Any())
{
try
{
entity.ResultsJson = JsonConvert.SerializeObject(bundleRequest.Results);
}
catch
{
entity.ResultsJson = "[]";
}
}
else
{
entity.ResultsJson = "[]";
}
await _context.SaveChangesAsync().ConfigureAwait(false);
}
}
public void DeleteBundleBacktestRequestByIdForUser(User user, string id)
{
var entity = _context.BundleBacktestRequests
.AsTracking()
.FirstOrDefault(b => b.RequestId == id && b.UserName == user.Name);
if (entity != null)
{
_context.BundleBacktestRequests.Remove(entity);
_context.SaveChanges();
}
}
public async Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id)
{
var entity = await _context.BundleBacktestRequests
.AsTracking()
.FirstOrDefaultAsync(b => b.RequestId == id && b.UserName == user.Name)
.ConfigureAwait(false);
if (entity != null)
{
_context.BundleBacktestRequests.Remove(entity);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
}
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status)
{
var entities = _context.BundleBacktestRequests
.AsNoTracking()
.Include(b => b.User)
.Where(b => b.Status == status)
.ToList();
return entities.Select(PostgreSqlMappers.Map);
}
public async Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus status)
{
var entities = await _context.BundleBacktestRequests
.AsNoTracking()
.Include(b => b.User)
.Where(b => b.Status == status)
.ToListAsync()
.ConfigureAwait(false);
return entities.Select(PostgreSqlMappers.Map);
}
}

View File

@@ -0,0 +1,91 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Bots;
using Microsoft.EntityFrameworkCore;
namespace Managing.Infrastructure.Databases.PostgreSql;
public class PostgreSqlBotRepository : IBotRepository
{
private readonly ManagingDbContext _context;
public PostgreSqlBotRepository(ManagingDbContext context)
{
_context = context;
}
public async Task InsertBotAsync(BotBackup bot)
{
bot.CreateDate = DateTime.UtcNow;
var entity = PostgreSqlMappers.Map(bot);
// Set the UserId if user is provided
if (bot.User != null)
{
var userEntity = await _context.Users
.AsNoTracking()
.FirstOrDefaultAsync(u => u.Name == bot.User.Name)
.ConfigureAwait(false);
entity.UserId = userEntity?.Id;
}
await _context.BotBackups.AddAsync(entity).ConfigureAwait(false);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
public async Task<IEnumerable<BotBackup>> GetBotsAsync()
{
var entities = await _context.BotBackups
.AsNoTracking()
.Include(m => m.User)
.ToListAsync()
.ConfigureAwait(false);
return PostgreSqlMappers.Map(entities);
}
public async Task UpdateBackupBot(BotBackup bot)
{
var existingEntity = await _context.BotBackups
.AsTracking()
.FirstOrDefaultAsync(b => b.Identifier == bot.Identifier)
.ConfigureAwait(false);
if (existingEntity == null)
{
throw new InvalidOperationException($"Bot backup with identifier '{bot.Identifier}' not found");
}
// Update the entity properties
existingEntity.Data = bot.SerializeData(); // Use the serialized data string
existingEntity.LastStatus = bot.LastStatus;
existingEntity.UpdatedAt = DateTime.UtcNow;
existingEntity.UserName = bot.User?.Name;
await _context.SaveChangesAsync().ConfigureAwait(false);
}
public async Task DeleteBotBackup(string identifier)
{
var entity = await _context.BotBackups
.AsTracking()
.FirstOrDefaultAsync(b => b.Identifier == identifier)
.ConfigureAwait(false);
if (entity == null)
{
throw new InvalidOperationException($"Bot backup with identifier '{identifier}' not found");
}
_context.BotBackups.Remove(entity);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
public async Task<BotBackup?> GetBotByIdentifierAsync(string identifier)
{
var entity = await _context.BotBackups
.AsNoTracking()
.Include(m => m.User)
.FirstOrDefaultAsync(b => b.Identifier == identifier)
.ConfigureAwait(false);
return PostgreSqlMappers.Map(entity);
}
}

View File

@@ -0,0 +1,156 @@
using System.Text.Json;
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Backtests;
using Managing.Domain.Users;
using Microsoft.EntityFrameworkCore;
namespace Managing.Infrastructure.Databases.PostgreSql;
public class PostgreSqlGeneticRepository : IGeneticRepository
{
private readonly ManagingDbContext _context;
public PostgreSqlGeneticRepository(ManagingDbContext context)
{
_context = context;
}
public void InsertGeneticRequestForUser(User user, GeneticRequest geneticRequest)
{
geneticRequest.User = user;
var geneticRequestEntity = PostgreSqlMappers.Map(geneticRequest);
// Handle User relationship - check if user exists or create new one
if (user != null)
{
var existingUser = _context.Users
.AsTracking() // Explicitly enable tracking for this operation
.FirstOrDefault(u => u.Name == user.Name);
if (existingUser != null)
{
geneticRequestEntity.UserId = existingUser.Id;
geneticRequestEntity.User = null; // Prevent EF from trying to insert duplicate user
}
else
{
// Let EF handle the new user creation
geneticRequestEntity.UserId = null;
}
}
_context.GeneticRequests.Add(geneticRequestEntity);
_context.SaveChanges();
}
public IEnumerable<GeneticRequest> GetGeneticRequestsByUser(User user)
{
// Use synchronous operations and AsNoTracking to avoid concurrency issues
var geneticRequestEntities = _context.GeneticRequests
.AsNoTracking()
.Include(gr => gr.User)
.Where(gr => gr.User != null && gr.User.Name == user.Name)
.OrderByDescending(gr => gr.CreatedAt)
.ToList();
return PostgreSqlMappers.Map(geneticRequestEntities);
}
public GeneticRequest GetGeneticRequestByIdForUser(User user, string id)
{
// Use synchronous operations and AsNoTracking to avoid concurrency issues
var geneticRequestEntity = _context.GeneticRequests
.AsNoTracking()
.Include(gr => gr.User)
.FirstOrDefault(gr => gr.RequestId == id);
// Check if genetic request exists and belongs to the user
if (geneticRequestEntity != null && geneticRequestEntity.User != null &&
geneticRequestEntity.User.Name == user.Name)
{
return PostgreSqlMappers.Map(geneticRequestEntity);
}
return null;
}
public async Task UpdateGeneticRequestAsync(GeneticRequest geneticRequest)
{
var existingEntity = _context.GeneticRequests
.Include(gr => gr.User)
.FirstOrDefault(gr => gr.RequestId == geneticRequest.RequestId);
if (existingEntity != null)
{
// Update the existing entity with new values
existingEntity.CompletedAt = geneticRequest.CompletedAt;
existingEntity.UpdatedAt = DateTime.UtcNow;
existingEntity.Status = geneticRequest.Status.ToString();
existingEntity.BestFitness = geneticRequest.BestFitness;
existingEntity.BestIndividual = geneticRequest.BestIndividual;
existingEntity.ErrorMessage = geneticRequest.ErrorMessage;
existingEntity.ProgressInfo = geneticRequest.ProgressInfo;
existingEntity.BestChromosome = geneticRequest.BestChromosome;
existingEntity.BestFitnessSoFar = geneticRequest.BestFitnessSoFar;
existingEntity.CurrentGeneration = geneticRequest.CurrentGeneration;
// Update EligibleIndicators JSON
if (geneticRequest.EligibleIndicators != null && geneticRequest.EligibleIndicators.Any())
{
try
{
existingEntity.EligibleIndicatorsJson = JsonSerializer.Serialize(geneticRequest.EligibleIndicators);
}
catch
{
existingEntity.EligibleIndicatorsJson = "[]";
}
}
else
{
existingEntity.EligibleIndicatorsJson = "[]";
}
// Only update the tracked entity, do not attach a new one
await _context.SaveChangesAsync();
}
}
public void DeleteGeneticRequestByIdForUser(User user, string id)
{
var geneticRequestEntity = _context.GeneticRequests
.Include(gr => gr.User)
.FirstOrDefault(gr => gr.RequestId == id);
if (geneticRequestEntity != null && geneticRequestEntity.User != null &&
geneticRequestEntity.User.Name == user.Name)
{
_context.GeneticRequests.Remove(geneticRequestEntity);
_context.SaveChanges();
}
}
public void DeleteAllGeneticRequestsForUser(User user)
{
var geneticRequestEntities = _context.GeneticRequests
.Include(gr => gr.User)
.Where(gr => gr.User != null && gr.User.Name == user.Name)
.ToList();
if (geneticRequestEntities.Any())
{
_context.GeneticRequests.RemoveRange(geneticRequestEntities);
_context.SaveChanges();
}
}
public async Task<List<GeneticRequest>> GetGeneticRequestsAsync(GeneticRequestStatus status)
{
var requests = await _context.GeneticRequests
.AsNoTracking()
.Include(gr => gr.User)
.Where(gr => gr.Status == status.ToString())
.OrderBy(gr => gr.CreatedAt)
.ToListAsync();
return PostgreSqlMappers.Map(requests).ToList();
}
}

View File

@@ -0,0 +1,916 @@
using Exilion.TradingAtomics;
using Managing.Domain.Accounts;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Statistics;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using Managing.Domain.Users;
using Managing.Domain.Workers;
using Managing.Infrastructure.Databases.PostgreSql.Entities;
using Newtonsoft.Json;
using static Managing.Common.Enums;
using SystemJsonSerializer = System.Text.Json.JsonSerializer;
namespace Managing.Infrastructure.Databases.PostgreSql;
public static class PostgreSqlMappers
{
#region Account Mappings
public static Account Map(AccountEntity entity)
{
if (entity == null) return null;
return new Account
{
Name = entity.Name,
Exchange = entity.Exchange,
Type = entity.Type,
Key = entity.Key,
Secret = entity.Secret,
User = entity.User != null ? Map(entity.User) : null,
Balances = new List<Balance>() // Empty list for now, balances handled separately if needed
};
}
public static AccountEntity Map(Account account)
{
if (account == null) return null;
return new AccountEntity
{
Name = account.Name,
Exchange = account.Exchange,
Type = account.Type,
Key = account.Key,
Secret = account.Secret,
User = account.User != null ? Map(account.User) : null
};
}
public static IEnumerable<Account> Map(IEnumerable<AccountEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<Account>();
}
#endregion
#region MoneyManagement Mappings
public static MoneyManagement Map(MoneyManagementEntity entity)
{
if (entity == null) return null;
return new MoneyManagement
{
Name = entity.Name,
Timeframe = entity.Timeframe,
StopLoss = entity.StopLoss,
TakeProfit = entity.TakeProfit,
Leverage = entity.Leverage,
User = entity.User != null ? Map(entity.User) : null
};
}
public static MoneyManagementEntity Map(MoneyManagement moneyManagement)
{
if (moneyManagement == null) return null;
return new MoneyManagementEntity
{
Name = moneyManagement.Name,
Timeframe = moneyManagement.Timeframe,
StopLoss = moneyManagement.StopLoss,
TakeProfit = moneyManagement.TakeProfit,
Leverage = moneyManagement.Leverage,
UserName = moneyManagement.User?.Name,
User = moneyManagement.User != null ? Map(moneyManagement.User) : null
};
}
public static IEnumerable<MoneyManagement> Map(IEnumerable<MoneyManagementEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<MoneyManagement>();
}
public static MoneyManagementEntity Map(LightMoneyManagement lightMoneyManagement)
{
if (lightMoneyManagement == null) return null;
return new MoneyManagementEntity
{
Name = lightMoneyManagement.Name,
Timeframe = lightMoneyManagement.Timeframe,
StopLoss = lightMoneyManagement.StopLoss,
TakeProfit = lightMoneyManagement.TakeProfit,
Leverage = lightMoneyManagement.Leverage
};
}
#endregion
#region User Mappings
public static User Map(UserEntity entity)
{
if (entity == null) return null;
return new User
{
Name = entity.Name,
AgentName = entity.AgentName,
AvatarUrl = entity.AvatarUrl,
TelegramChannel = entity.TelegramChannel
};
}
public static UserEntity Map(User user)
{
if (user == null) return null;
return new UserEntity
{
Name = user.Name,
AgentName = user.AgentName,
AvatarUrl = user.AvatarUrl,
TelegramChannel = user.TelegramChannel
};
}
#endregion
#region GeneticRequest Mappings
public static GeneticRequest Map(GeneticRequestEntity entity)
{
if (entity == null) return null;
var geneticRequest = new GeneticRequest(entity.RequestId)
{
User = entity.User != null ? Map(entity.User) : null,
CreatedAt = entity.CreatedAt,
CompletedAt = entity.CompletedAt,
Status = Enum.Parse<GeneticRequestStatus>(entity.Status),
Ticker = entity.Ticker,
Timeframe = entity.Timeframe,
StartDate = entity.StartDate,
EndDate = entity.EndDate,
Balance = entity.Balance,
PopulationSize = entity.PopulationSize,
Generations = entity.Generations,
MutationRate = entity.MutationRate,
SelectionMethod = entity.SelectionMethod,
CrossoverMethod = entity.CrossoverMethod,
MutationMethod = entity.MutationMethod,
ElitismPercentage = entity.ElitismPercentage,
MaxTakeProfit = entity.MaxTakeProfit,
BestFitness = entity.BestFitness,
BestIndividual = entity.BestIndividual,
ErrorMessage = entity.ErrorMessage,
ProgressInfo = entity.ProgressInfo,
BestChromosome = entity.BestChromosome,
BestFitnessSoFar = entity.BestFitnessSoFar,
CurrentGeneration = entity.CurrentGeneration
};
// Deserialize EligibleIndicators from JSON
if (!string.IsNullOrEmpty(entity.EligibleIndicatorsJson))
{
try
{
geneticRequest.EligibleIndicators = SystemJsonSerializer.Deserialize<List<IndicatorType>>(entity.EligibleIndicatorsJson) ?? new List<IndicatorType>();
}
catch
{
geneticRequest.EligibleIndicators = new List<IndicatorType>();
}
}
return geneticRequest;
}
public static GeneticRequestEntity Map(GeneticRequest geneticRequest)
{
if (geneticRequest == null) return null;
var entity = new GeneticRequestEntity
{
RequestId = geneticRequest.RequestId,
User = geneticRequest.User != null ? Map(geneticRequest.User) : null,
CreatedAt = geneticRequest.CreatedAt,
CompletedAt = geneticRequest.CompletedAt,
UpdatedAt = DateTime.UtcNow,
Status = geneticRequest.Status.ToString(),
Ticker = geneticRequest.Ticker,
Timeframe = geneticRequest.Timeframe,
StartDate = geneticRequest.StartDate,
EndDate = geneticRequest.EndDate,
Balance = geneticRequest.Balance,
PopulationSize = geneticRequest.PopulationSize,
Generations = geneticRequest.Generations,
MutationRate = geneticRequest.MutationRate,
SelectionMethod = geneticRequest.SelectionMethod,
CrossoverMethod = geneticRequest.CrossoverMethod,
MutationMethod = geneticRequest.MutationMethod,
ElitismPercentage = geneticRequest.ElitismPercentage,
MaxTakeProfit = geneticRequest.MaxTakeProfit,
BestFitness = geneticRequest.BestFitness,
BestIndividual = geneticRequest.BestIndividual,
ErrorMessage = geneticRequest.ErrorMessage,
ProgressInfo = geneticRequest.ProgressInfo,
BestChromosome = geneticRequest.BestChromosome,
BestFitnessSoFar = geneticRequest.BestFitnessSoFar,
CurrentGeneration = geneticRequest.CurrentGeneration
};
// Serialize EligibleIndicators to JSON
if (geneticRequest.EligibleIndicators != null && geneticRequest.EligibleIndicators.Any())
{
try
{
entity.EligibleIndicatorsJson = SystemJsonSerializer.Serialize(geneticRequest.EligibleIndicators);
}
catch
{
entity.EligibleIndicatorsJson = "[]";
}
}
else
{
entity.EligibleIndicatorsJson = "[]";
}
return entity;
}
public static IEnumerable<GeneticRequest> Map(IEnumerable<GeneticRequestEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<GeneticRequest>();
}
#endregion
#region Backtest Mappings
public static Backtest Map(BacktestEntity entity)
{
if (entity == null) return null;
// Deserialize JSON fields using MongoMappers for compatibility
var config = JsonConvert.DeserializeObject<TradingBotConfig>(entity.ConfigJson);
var positions = JsonConvert.DeserializeObject<List<Position>>(entity.PositionsJson) ?? new List<Position>();
var signals = JsonConvert.DeserializeObject<List<LightSignal>>(entity.SignalsJson) ?? new List<LightSignal>();
var statistics = !string.IsNullOrEmpty(entity.StatisticsJson)
? JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson)
: null;
var backtest = new Backtest(config, positions, signals)
{
Id = entity.Identifier,
FinalPnl = entity.FinalPnl,
WinRate = entity.WinRate,
GrowthPercentage = entity.GrowthPercentage,
HodlPercentage = entity.HodlPercentage,
StartDate = entity.StartDate,
EndDate = entity.EndDate,
User = new User { Name = entity.UserName },
Statistics = statistics,
Fees = entity.Fees,
Score = entity.Score,
ScoreMessage = entity.ScoreMessage,
RequestId = entity.RequestId,
Metadata = entity.Metadata
};
return backtest;
}
public static BacktestEntity Map(Backtest backtest)
{
if (backtest == null) return null;
return new BacktestEntity
{
Identifier = backtest.Id,
RequestId = backtest.RequestId,
FinalPnl = backtest.FinalPnl,
WinRate = backtest.WinRate,
GrowthPercentage = backtest.GrowthPercentage,
HodlPercentage = backtest.HodlPercentage,
ConfigJson = JsonConvert.SerializeObject(backtest.Config),
PositionsJson = JsonConvert.SerializeObject(backtest.Positions),
SignalsJson = JsonConvert.SerializeObject(backtest.Signals),
StartDate = backtest.StartDate,
EndDate = backtest.EndDate,
MoneyManagementJson = JsonConvert.SerializeObject(backtest.Config?.MoneyManagement),
UserName = backtest.User?.Name ?? string.Empty,
StatisticsJson = backtest.Statistics != null ? JsonConvert.SerializeObject(backtest.Statistics) : null,
Fees = backtest.Fees,
Score = backtest.Score,
ScoreMessage = backtest.ScoreMessage ?? string.Empty,
Metadata = backtest.Metadata?.ToString(),
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
}
public static IEnumerable<Backtest> Map(IEnumerable<BacktestEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<Backtest>();
}
#endregion
#region BundleBacktestRequest Mappings
public static BundleBacktestRequest Map(BundleBacktestRequestEntity entity)
{
if (entity == null) return null;
var bundleRequest = new BundleBacktestRequest(entity.RequestId)
{
User = entity.User != null ? Map(entity.User) : new User { Name = entity.UserName },
CreatedAt = entity.CreatedAt,
CompletedAt = entity.CompletedAt,
Status = entity.Status,
BacktestRequestsJson = entity.BacktestRequestsJson,
TotalBacktests = entity.TotalBacktests,
CompletedBacktests = entity.CompletedBacktests,
FailedBacktests = entity.FailedBacktests,
ErrorMessage = entity.ErrorMessage,
ProgressInfo = entity.ProgressInfo,
CurrentBacktest = entity.CurrentBacktest,
EstimatedTimeRemainingSeconds = entity.EstimatedTimeRemainingSeconds,
Name = entity.Name
};
// Deserialize Results from JSON
if (!string.IsNullOrEmpty(entity.ResultsJson))
{
try
{
bundleRequest.Results = JsonConvert.DeserializeObject<List<string>>(entity.ResultsJson) ?? new List<string>();
}
catch
{
bundleRequest.Results = new List<string>();
}
}
return bundleRequest;
}
public static BundleBacktestRequestEntity Map(BundleBacktestRequest bundleRequest)
{
if (bundleRequest == null) return null;
var entity = new BundleBacktestRequestEntity
{
RequestId = bundleRequest.RequestId,
UserName = bundleRequest.User?.Name ?? string.Empty,
UserId = null, // Will be set by the repository when saving
CreatedAt = bundleRequest.CreatedAt,
CompletedAt = bundleRequest.CompletedAt,
Status = bundleRequest.Status,
BacktestRequestsJson = bundleRequest.BacktestRequestsJson,
TotalBacktests = bundleRequest.TotalBacktests,
CompletedBacktests = bundleRequest.CompletedBacktests,
FailedBacktests = bundleRequest.FailedBacktests,
ErrorMessage = bundleRequest.ErrorMessage,
ProgressInfo = bundleRequest.ProgressInfo,
CurrentBacktest = bundleRequest.CurrentBacktest,
EstimatedTimeRemainingSeconds = bundleRequest.EstimatedTimeRemainingSeconds,
Name = bundleRequest.Name,
UpdatedAt = DateTime.UtcNow
};
// Serialize Results to JSON
if (bundleRequest.Results != null && bundleRequest.Results.Any())
{
try
{
entity.ResultsJson = JsonConvert.SerializeObject(bundleRequest.Results);
}
catch
{
entity.ResultsJson = "[]";
}
}
else
{
entity.ResultsJson = "[]";
}
return entity;
}
public static IEnumerable<BundleBacktestRequest> Map(IEnumerable<BundleBacktestRequestEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<BundleBacktestRequest>();
}
#endregion
#region Trading Mappings
// Scenario mappings
public static Scenario Map(ScenarioEntity entity)
{
if (entity == null) return null;
return new Scenario(entity.Name, entity.LoopbackPeriod)
{
User = entity.UserName != null ? new User { Name = entity.UserName } : null,
Indicators = new List<Indicator>() // Will be populated separately when needed
};
}
public static ScenarioEntity Map(Scenario scenario)
{
if (scenario == null) return null;
return new ScenarioEntity
{
Name = scenario.Name,
LoopbackPeriod = scenario.LoopbackPeriod ?? 1,
UserName = scenario.User?.Name
};
}
// Indicator mappings
public static Indicator Map(IndicatorEntity entity)
{
if (entity == null) return null;
return new Indicator(entity.Name, entity.Type)
{
SignalType = entity.SignalType,
MinimumHistory = entity.MinimumHistory,
Period = entity.Period,
FastPeriods = entity.FastPeriods,
SlowPeriods = entity.SlowPeriods,
SignalPeriods = entity.SignalPeriods,
Multiplier = entity.Multiplier,
SmoothPeriods = entity.SmoothPeriods,
StochPeriods = entity.StochPeriods,
CyclePeriods = entity.CyclePeriods,
User = entity.UserName != null ? new User { Name = entity.UserName } : null
};
}
public static IndicatorEntity Map(Indicator indicator)
{
if (indicator == null) return null;
return new IndicatorEntity
{
Name = indicator.Name,
Type = indicator.Type,
Timeframe = Timeframe.FifteenMinutes, // Default timeframe
SignalType = indicator.SignalType,
MinimumHistory = indicator.MinimumHistory,
Period = indicator.Period,
FastPeriods = indicator.FastPeriods,
SlowPeriods = indicator.SlowPeriods,
SignalPeriods = indicator.SignalPeriods,
Multiplier = indicator.Multiplier,
SmoothPeriods = indicator.SmoothPeriods,
StochPeriods = indicator.StochPeriods,
CyclePeriods = indicator.CyclePeriods,
UserName = indicator.User?.Name
};
}
// Signal mappings
public static Signal Map(SignalEntity entity)
{
if (entity == null) return null;
var candle = !string.IsNullOrEmpty(entity.CandleJson)
? JsonConvert.DeserializeObject<Candle>(entity.CandleJson)
: null;
return new Signal(
entity.Ticker,
entity.Direction,
entity.Confidence,
candle,
entity.Date,
TradingExchanges.Evm, // Default exchange
entity.Type,
entity.SignalType,
entity.IndicatorName,
entity.UserName != null ? new User { Name = entity.UserName } : null)
{
Status = entity.Status
};
}
public static SignalEntity Map(Signal signal)
{
if (signal == null) return null;
return new SignalEntity
{
Identifier = signal.Identifier,
Direction = signal.Direction,
Confidence = signal.Confidence,
Date = signal.Date,
Ticker = signal.Ticker,
Status = signal.Status,
Timeframe = signal.Timeframe,
Type = signal.IndicatorType,
SignalType = signal.SignalType,
IndicatorName = signal.IndicatorName,
UserName = signal.User?.Name,
CandleJson = signal.Candle != null ? JsonConvert.SerializeObject(signal.Candle) : null
};
}
// Position mappings
public static Position Map(PositionEntity entity)
{
if (entity == null) return null;
// Deserialize money management
var moneyManagement = new MoneyManagement(); // Default money management
if (!string.IsNullOrEmpty(entity.MoneyManagementJson))
{
moneyManagement = JsonConvert.DeserializeObject<MoneyManagement>(entity.MoneyManagementJson) ?? new MoneyManagement();
}
var position = new Position(
entity.Identifier,
entity.AccountName,
entity.OriginDirection,
entity.Ticker,
moneyManagement,
entity.Initiator,
entity.Date,
entity.UserName != null ? new User { Name = entity.UserName } : null)
{
Status = entity.Status,
SignalIdentifier = entity.SignalIdentifier
};
// Set ProfitAndLoss with proper type
position.ProfitAndLoss = new ProfitAndLoss { Realized = entity.ProfitAndLoss };
// Map related trades
if (entity.OpenTrade != null)
position.Open = Map(entity.OpenTrade);
if (entity.StopLossTrade != null)
position.StopLoss = Map(entity.StopLossTrade);
if (entity.TakeProfit1Trade != null)
position.TakeProfit1 = Map(entity.TakeProfit1Trade);
if (entity.TakeProfit2Trade != null)
position.TakeProfit2 = Map(entity.TakeProfit2Trade);
return position;
}
public static PositionEntity Map(Position position)
{
if (position == null) return null;
return new PositionEntity
{
Identifier = position.Identifier,
Date = position.Date,
ProfitAndLoss = position.ProfitAndLoss?.Realized ?? 0,
OriginDirection = position.OriginDirection,
Status = position.Status,
Ticker = position.Ticker,
Initiator = position.Initiator,
SignalIdentifier = position.SignalIdentifier,
AccountName = position.AccountName,
UserName = position.User?.Name,
MoneyManagementJson = position.MoneyManagement != null ? JsonConvert.SerializeObject(position.MoneyManagement) : null
};
}
// Trade mappings
public static Trade Map(TradeEntity entity)
{
if (entity == null) return null;
return new Trade(
entity.Date,
entity.Direction,
entity.Status,
entity.TradeType,
entity.Ticker,
entity.Quantity,
entity.Price,
entity.Leverage,
entity.ExchangeOrderId,
entity.Message)
{
Fee = entity.Fee
};
}
public static TradeEntity Map(Trade trade)
{
if (trade == null) return null;
return new TradeEntity
{
Date = trade.Date,
Direction = trade.Direction,
Status = trade.Status,
TradeType = trade.TradeType,
Ticker = trade.Ticker,
Fee = trade.Fee,
Quantity = trade.Quantity,
Price = trade.Price,
Leverage = trade.Leverage,
ExchangeOrderId = trade.ExchangeOrderId,
Message = trade.Message
};
}
// Collection mappings
public static IEnumerable<Scenario> Map(IEnumerable<ScenarioEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<Scenario>();
}
public static IEnumerable<Indicator> Map(IEnumerable<IndicatorEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<Indicator>();
}
public static IEnumerable<Signal> Map(IEnumerable<SignalEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<Signal>();
}
public static IEnumerable<Position> Map(IEnumerable<PositionEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<Position>();
}
#endregion
#region Bot Mappings
// BotBackup mappings
public static BotBackup Map(BotBackupEntity entity)
{
if (entity == null) return null;
var botBackup = new BotBackup
{
Identifier = entity.Identifier,
User = entity.User != null ? Map(entity.User) : null,
LastStatus = entity.LastStatus,
CreateDate = entity.CreateDate
};
// Deserialize the JSON data using the helper method
botBackup.DeserializeData(entity.Data);
return botBackup;
}
public static BotBackupEntity Map(BotBackup botBackup)
{
if (botBackup == null) return null;
return new BotBackupEntity
{
Identifier = botBackup.Identifier,
UserName = botBackup.User?.Name,
User = botBackup.User != null ? Map(botBackup.User) : null,
Data = botBackup.SerializeData(), // Serialize the data using the helper method
LastStatus = botBackup.LastStatus,
CreateDate = botBackup.CreateDate,
UpdatedAt = DateTime.UtcNow
};
}
public static IEnumerable<BotBackup> Map(IEnumerable<BotBackupEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<BotBackup>();
}
public static IEnumerable<BotBackupEntity> Map(IEnumerable<BotBackup> botBackups)
{
return botBackups?.Select(Map) ?? Enumerable.Empty<BotBackupEntity>();
}
#endregion
#region Statistics Mappings
// TopVolumeTicker mappings
public static TopVolumeTicker Map(TopVolumeTickerEntity entity)
{
if (entity == null) return null;
return new TopVolumeTicker
{
Ticker = entity.Ticker,
Date = entity.Date,
Volume = entity.Volume,
Rank = entity.Rank,
Exchange = entity.Exchange
};
}
public static TopVolumeTickerEntity Map(TopVolumeTicker topVolumeTicker)
{
if (topVolumeTicker == null) return null;
return new TopVolumeTickerEntity
{
Ticker = topVolumeTicker.Ticker,
Date = topVolumeTicker.Date,
Volume = topVolumeTicker.Volume,
Rank = topVolumeTicker.Rank,
Exchange = topVolumeTicker.Exchange
};
}
public static IEnumerable<TopVolumeTicker> Map(IEnumerable<TopVolumeTickerEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<TopVolumeTicker>();
}
// SpotlightOverview mappings
public static SpotlightOverview Map(SpotlightOverviewEntity entity)
{
if (entity == null) return null;
var overview = new SpotlightOverview
{
Identifier = entity.Identifier,
DateTime = entity.DateTime,
ScenarioCount = entity.ScenarioCount,
Spotlights = new List<Spotlight>()
};
// Deserialize the JSON spotlights data
if (!string.IsNullOrEmpty(entity.SpotlightsJson))
{
try
{
overview.Spotlights = SystemJsonSerializer.Deserialize<List<Spotlight>>(entity.SpotlightsJson) ?? new List<Spotlight>();
}
catch (JsonException)
{
// If deserialization fails, return empty list
overview.Spotlights = new List<Spotlight>();
}
}
return overview;
}
public static SpotlightOverviewEntity Map(SpotlightOverview overview)
{
if (overview == null) return null;
var entity = new SpotlightOverviewEntity
{
Identifier = overview.Identifier,
DateTime = overview.DateTime,
ScenarioCount = overview.ScenarioCount
};
// Serialize the spotlights to JSON
if (overview.Spotlights != null)
{
entity.SpotlightsJson = SystemJsonSerializer.Serialize(overview.Spotlights);
}
return entity;
}
public static IEnumerable<SpotlightOverview> Map(IEnumerable<SpotlightOverviewEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<SpotlightOverview>();
}
// Trader mappings
public static Trader Map(TraderEntity entity)
{
if (entity == null) return null;
return new Trader
{
Address = entity.Address,
Winrate = entity.Winrate,
Pnl = entity.Pnl,
TradeCount = entity.TradeCount,
AverageWin = entity.AverageWin,
AverageLoss = entity.AverageLoss,
Roi = entity.Roi
};
}
public static TraderEntity Map(Trader trader, bool isBestTrader)
{
if (trader == null) return null;
return new TraderEntity
{
Address = trader.Address,
Winrate = trader.Winrate,
Pnl = trader.Pnl,
TradeCount = trader.TradeCount,
AverageWin = trader.AverageWin,
AverageLoss = trader.AverageLoss,
Roi = trader.Roi,
IsBestTrader = isBestTrader
};
}
public static IEnumerable<Trader> Map(IEnumerable<TraderEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<Trader>();
}
// FundingRate mappings
public static FundingRate Map(FundingRateEntity entity)
{
if (entity == null) return null;
return new FundingRate
{
Ticker = entity.Ticker,
Exchange = entity.Exchange,
Rate = entity.Rate,
OpenInterest = entity.OpenInterest,
Date = entity.Date,
Direction = entity.Direction
};
}
public static FundingRateEntity Map(FundingRate fundingRate)
{
if (fundingRate == null) return null;
return new FundingRateEntity
{
Ticker = fundingRate.Ticker,
Exchange = fundingRate.Exchange,
Rate = fundingRate.Rate,
OpenInterest = fundingRate.OpenInterest,
Date = fundingRate.Date,
Direction = fundingRate.Direction
};
}
public static IEnumerable<FundingRate> Map(IEnumerable<FundingRateEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<FundingRate>();
}
#endregion
#region Worker Mappings
public static Worker Map(WorkerEntity entity)
{
if (entity == null) return null;
return new Worker
{
WorkerType = entity.WorkerType,
StartTime = entity.StartTime,
LastRunTime = entity.LastRunTime,
ExecutionCount = entity.ExecutionCount,
Delay = TimeSpan.FromTicks(entity.DelayTicks),
IsActive = entity.IsActive
};
}
public static WorkerEntity Map(Worker worker)
{
if (worker == null) return null;
return new WorkerEntity
{
WorkerType = worker.WorkerType,
StartTime = worker.StartTime,
LastRunTime = worker.LastRunTime,
ExecutionCount = worker.ExecutionCount,
DelayTicks = worker.Delay.Ticks,
IsActive = worker.IsActive
};
}
public static IEnumerable<Worker> Map(IEnumerable<WorkerEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<Worker>();
}
#endregion
}

View File

@@ -0,0 +1,173 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
using Microsoft.EntityFrameworkCore;
namespace Managing.Infrastructure.Databases.PostgreSql;
public class PostgreSqlSettingsRepository : ISettingsRepository
{
private readonly ManagingDbContext _context;
public PostgreSqlSettingsRepository(ManagingDbContext context)
{
_context = context;
}
public async Task DeleteMoneyManagementAsync(string name)
{
var moneyManagement = await _context.MoneyManagements
.AsTracking()
.FirstOrDefaultAsync(m => m.Name == name)
.ConfigureAwait(false);
if (moneyManagement != null)
{
_context.MoneyManagements.Remove(moneyManagement);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
}
public async Task DeleteMoneyManagementsAsync()
{
var moneyManagements = await _context.MoneyManagements
.AsTracking()
.ToListAsync()
.ConfigureAwait(false);
_context.MoneyManagements.RemoveRange(moneyManagements);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
public async Task<MoneyManagement> GetMoneyManagement(string name)
{
var moneyManagement = await _context.MoneyManagements
.AsNoTracking()
.Include(m => m.User)
.FirstOrDefaultAsync(m => m.Name == name)
.ConfigureAwait(false);
return PostgreSqlMappers.Map(moneyManagement);
}
public async Task<IEnumerable<MoneyManagement>> GetMoneyManagementsAsync()
{
var moneyManagements = await _context.MoneyManagements
.AsNoTracking()
.Include(m => m.User)
.ToListAsync()
.ConfigureAwait(false);
return PostgreSqlMappers.Map(moneyManagements);
}
public async Task InsertMoneyManagement(LightMoneyManagement request, User user)
{
// Check if money management already exists for the same user
var existingMoneyManagement = await _context.MoneyManagements
.AsNoTracking()
.FirstOrDefaultAsync(m => m.Name == request.Name &&
((user == null && m.UserName == null) ||
(user != null && m.UserName == user.Name)))
.ConfigureAwait(false);
if (existingMoneyManagement != null)
{
throw new InvalidOperationException(
$"Money management with name '{request.Name}' already exists for user '{user?.Name}'");
}
var entity = PostgreSqlMappers.Map(request);
entity.UserName = user?.Name;
// Set the UserId if user is provided
if (user != null)
{
var userEntity = await _context.Users
.AsNoTracking()
.FirstOrDefaultAsync(u => u.Name == user.Name)
.ConfigureAwait(false);
entity.UserId = userEntity?.Id;
}
await _context.MoneyManagements.AddAsync(entity).ConfigureAwait(false);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
public async Task UpdateMoneyManagementAsync(LightMoneyManagement moneyManagement, User user)
{
var entity = await _context.MoneyManagements
.AsTracking()
.FirstOrDefaultAsync(m => m.Name == moneyManagement.Name &&
(user != null && m.UserName == user.Name))
.ConfigureAwait(false);
if (entity != null)
{
entity.Timeframe = moneyManagement.Timeframe;
entity.StopLoss = moneyManagement.StopLoss;
entity.TakeProfit = moneyManagement.TakeProfit;
entity.Leverage = moneyManagement.Leverage;
entity.UserName = user?.Name;
entity.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync().ConfigureAwait(false);
}
}
// User-specific implementations
public async Task<MoneyManagement> GetMoneyManagementByUser(User user, string name)
{
var moneyManagement = await _context.MoneyManagements
.AsNoTracking()
.Include(m => m.User)
.FirstOrDefaultAsync(m =>
m.Name == name &&
m.UserName != null &&
m.UserName == user.Name)
.ConfigureAwait(false);
return PostgreSqlMappers.Map(moneyManagement);
}
public async Task<IEnumerable<MoneyManagement>> GetMoneyManagementsByUserAsync(User user)
{
var moneyManagements = await _context.MoneyManagements
.AsNoTracking()
.Include(m => m.User)
.Where(m => m.UserName != null && m.UserName == user.Name)
.ToListAsync()
.ConfigureAwait(false);
return PostgreSqlMappers.Map(moneyManagements);
}
public async Task DeleteMoneyManagementByUserAsync(User user, string name)
{
var moneyManagement = await _context.MoneyManagements
.AsTracking()
.FirstOrDefaultAsync(m =>
m.Name == name &&
m.UserName != null &&
m.UserName == user.Name)
.ConfigureAwait(false);
if (moneyManagement != null)
{
_context.MoneyManagements.Remove(moneyManagement);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
}
public async Task DeleteMoneyManagementsByUserAsync(User user)
{
var moneyManagements = await _context.MoneyManagements
.AsTracking()
.Where(m => m.UserName != null && m.UserName == user.Name)
.ToListAsync()
.ConfigureAwait(false);
_context.MoneyManagements.RemoveRange(moneyManagements);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
}

Some files were not shown because too many files have changed in this diff Show More