Trading bot grain (#33)

* Trading bot Grain

* Fix a bit more of the trading bot

* Advance on the tradingbot grain

* Fix build

* Fix db script

* Fix user login

* Fix a bit backtest

* Fix cooldown and backtest

* start fixing bot start

* Fix startup

* Setup local db

* Fix build and update candles and scenario

* Add bot registry

* Add reminder

* Updateing the grains

* fix bootstraping

* Save stats on tick

* Save bot data every tick

* Fix serialization

* fix save bot stats

* Fix get candles

* use dict instead of list for position

* Switch hashset to dict

* Fix a bit

* Fix bot launch and bot view

* add migrations

* Remove the tolist

* Add agent grain

* Save agent summary

* clean

* Add save bot

* Update get bots

* Add get bots

* Fix stop/restart

* fix Update config

* Update scanner table on new backtest saved

* Fix backtestRowDetails.tsx

* Fix agentIndex

* Update agentIndex

* Fix more things

* Update user cache

* Fix

* Fix account load/start/restart/run
This commit is contained in:
Oda
2025-08-04 23:07:06 +02:00
committed by GitHub
parent cd378587aa
commit 082ae8714b
215 changed files with 9562 additions and 14028 deletions

View File

@@ -23,11 +23,12 @@ public class CandleRepository : ICandleRepository
_logger = logger;
}
public async Task<IList<Candle>> GetCandles(
public async Task<HashSet<Candle>> GetCandles(
TradingExchanges exchange,
Ticker ticker,
Timeframe timeframe,
DateTime start)
DateTime start,
int? limit = null)
{
var results = await _influxDbRepository.QueryAsync(async query =>
{
@@ -37,20 +38,25 @@ public class CandleRepository : ICandleRepository
$"|> filter(fn: (r) => r[\"ticker\"] == \"{ticker}\")" +
$"|> filter(fn: (r) => r[\"timeframe\"] == \"{timeframe}\")" +
$"|> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")";
if (limit != null)
{
flux += $"|> tail(n:{limit})";
}
var prices = await query.QueryAsync<PriceDto>(flux, _influxDbRepository.Organization);
return prices.Select(price => PriceHelpers.Map(price)).ToList();
return prices.Select(price => PriceHelpers.Map(price)).ToHashSet();
});
return results;
}
public async Task<IList<Candle>> GetCandles(
public async Task<HashSet<Candle>> GetCandles(
TradingExchanges exchange,
Ticker ticker,
Timeframe timeframe,
DateTime start,
DateTime end)
DateTime end,
int? limit = null)
{
var results = await _influxDbRepository.QueryAsync(async query =>
{
@@ -60,9 +66,13 @@ public class CandleRepository : ICandleRepository
$"|> filter(fn: (r) => r[\"ticker\"] == \"{ticker}\")" +
$"|> filter(fn: (r) => r[\"timeframe\"] == \"{timeframe}\")" +
$"|> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")";
if (limit != null)
{
flux += $"|> tail(n:{limit})";
}
var prices = await query.QueryAsync<PriceDto>(flux, _influxDbRepository.Organization);
return prices.Select(price => PriceHelpers.Map(price)).ToList();
return prices.Select(price => PriceHelpers.Map(price)).ToHashSet();
});
return results;

View File

@@ -1,34 +0,0 @@
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");
}
}
}

View File

@@ -1,47 +0,0 @@
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");
}
}
}

View File

@@ -1,85 +0,0 @@
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

@@ -1,49 +0,0 @@
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

@@ -1,49 +0,0 @@
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");
}
}
}

View File

@@ -1,49 +0,0 @@
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");
}
}
}

View File

@@ -1,51 +0,0 @@
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");
}
}
}

View File

@@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Managing.Infrastructure.Databases.Migrations
{
[DbContext(typeof(ManagingDbContext))]
[Migration("20250725202808_RemoveFeeEntity")]
partial class RemoveFeeEntity
[Migration("20250801100607_Init")]
partial class Init
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -177,55 +177,68 @@ namespace Managing.Infrastructure.Databases.Migrations
b.ToTable("Backtests");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b =>
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b =>
{
b.Property<int>("Id")
b.Property<Guid>("Identifier")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
.HasMaxLength(255)
.HasColumnType("uuid");
b.Property<DateTime>("CreateDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("text");
b.Property<decimal>("Fees")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<string>("Identifier")
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<int>("LastStatus")
b.Property<decimal>("Pnl")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<decimal>("Roi")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<DateTime>("StartupTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.Property<int>("TradeLosses")
.HasColumnType("integer");
b.Property<int>("TradeWins")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("UserId")
b.Property<int>("UserId")
.HasColumnType("integer");
b.Property<string>("UserName")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<decimal>("Volume")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.HasKey("Id");
b.HasKey("Identifier");
b.HasIndex("CreateDate");
b.HasIndex("Identifier")
.IsUnique();
b.HasIndex("LastStatus");
b.HasIndex("Status");
b.HasIndex("UserId");
b.HasIndex("UserName");
b.HasIndex("UserName", "CreateDate");
b.ToTable("BotBackups");
b.ToTable("Bots");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b =>
@@ -619,11 +632,9 @@ namespace Managing.Infrastructure.Databases.Migrations
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b =>
{
b.Property<int>("Id")
b.Property<Guid>("Identifier")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
.HasColumnType("uuid");
b.Property<string>("AccountName")
.IsRequired()
@@ -636,11 +647,6 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<DateTime>("Date")
.HasColumnType("timestamp with time zone");
b.Property<string>("Identifier")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("Initiator")
.IsRequired()
.HasColumnType("text");
@@ -687,7 +693,7 @@ namespace Managing.Infrastructure.Databases.Migrations
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.HasKey("Id");
b.HasKey("Identifier");
b.HasIndex("CreatedAt");
@@ -1185,6 +1191,7 @@ namespace Managing.Infrastructure.Databases.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("AgentName")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
@@ -1251,12 +1258,13 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b =>
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
.OnDelete(DeleteBehavior.SetNull)
.IsRequired();
b.Navigation("User");
});

View File

@@ -7,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
public partial class Init : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -34,7 +34,7 @@ namespace Managing.Infrastructure.Databases.Migrations
StatisticsJson = table.Column<string>(type: "jsonb", nullable: true),
Fees = table.Column<decimal>(type: "numeric(18,8)", nullable: false),
Score = table.Column<double>(type: "double precision", nullable: false),
ScoreMessage = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: false),
ScoreMessage = table.Column<string>(type: "text", maxLength: 1000, nullable: false),
Metadata = table.Column<string>(type: "text", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
@@ -44,69 +44,6 @@ namespace Managing.Infrastructure.Databases.Migrations
table.PrimaryKey("PK_Backtests", x => x.Id);
});
migrationBuilder.CreateTable(
name: "BotBackups",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Identifier = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
UserName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
Data = table.Column<string>(type: "jsonb", nullable: false),
LastStatus = table.Column<int>(type: "integer", nullable: false),
CreateDate = 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_BotBackups", x => x.Id);
});
migrationBuilder.CreateTable(
name: "BundleBacktestRequests",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
RequestId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
UserName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
CompletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Status = table.Column<string>(type: "text", nullable: false),
BacktestRequestsJson = table.Column<string>(type: "text", nullable: false),
TotalBacktests = table.Column<int>(type: "integer", nullable: false),
CompletedBacktests = table.Column<int>(type: "integer", nullable: false),
FailedBacktests = table.Column<int>(type: "integer", nullable: false),
ErrorMessage = table.Column<string>(type: "text", nullable: true),
ProgressInfo = table.Column<string>(type: "text", nullable: true),
CurrentBacktest = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
EstimatedTimeRemainingSeconds = table.Column<int>(type: "integer", nullable: true),
Name = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
ResultsJson = table.Column<string>(type: "jsonb", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BundleBacktestRequests", x => x.Id);
});
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),
Exchange = table.Column<string>(type: "text", nullable: false),
LastUpdate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
CreatedAt = 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.CreateTable(
name: "FundingRates",
columns: table => new
@@ -155,26 +92,6 @@ namespace Managing.Infrastructure.Databases.Migrations
table.PrimaryKey("PK_Indicators", x => x.Id);
});
migrationBuilder.CreateTable(
name: "MoneyManagements",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
Timeframe = table.Column<string>(type: "text", nullable: false),
StopLoss = table.Column<decimal>(type: "numeric(18,8)", nullable: false),
TakeProfit = table.Column<decimal>(type: "numeric(18,8)", nullable: false),
Leverage = table.Column<decimal>(type: "numeric(18,8)", nullable: false),
UserName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
CreatedAt = 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_MoneyManagements", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Scenarios",
columns: table => new
@@ -236,6 +153,44 @@ namespace Managing.Infrastructure.Databases.Migrations
table.PrimaryKey("PK_SpotlightOverviews", x => x.Id);
});
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.CreateTable(
name: "TopVolumeTickers",
columns: table => new
@@ -309,7 +264,7 @@ namespace Managing.Infrastructure.Databases.Migrations
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
AgentName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
AgentName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
AvatarUrl = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
TelegramChannel = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true)
},
@@ -318,6 +273,24 @@ namespace Managing.Infrastructure.Databases.Migrations
table.PrimaryKey("PK_Users", x => x.Id);
});
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.CreateTable(
name: "ScenarioIndicators",
columns: table => new
@@ -349,9 +322,7 @@ namespace Managing.Infrastructure.Databases.Migrations
name: "Positions",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Identifier = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
Identifier = table.Column<Guid>(type: "uuid", nullable: false),
Date = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ProfitAndLoss = table.Column<decimal>(type: "numeric(18,8)", nullable: false),
OriginDirection = table.Column<string>(type: "text", nullable: false),
@@ -371,7 +342,7 @@ namespace Managing.Infrastructure.Databases.Migrations
},
constraints: table =>
{
table.PrimaryKey("PK_Positions", x => x.Id);
table.PrimaryKey("PK_Positions", x => x.Identifier);
table.ForeignKey(
name: "FK_Positions_Trades_OpenTradeId",
column: x => x.OpenTradeId,
@@ -422,6 +393,70 @@ namespace Managing.Infrastructure.Databases.Migrations
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "Bots",
columns: table => new
{
Identifier = table.Column<Guid>(type: "uuid", maxLength: 255, nullable: false),
Name = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
UserId = table.Column<int>(type: "integer", nullable: false),
Status = table.Column<string>(type: "text", nullable: false),
CreateDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
StartupTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
TradeWins = table.Column<int>(type: "integer", nullable: false),
TradeLosses = table.Column<int>(type: "integer", nullable: false),
Pnl = table.Column<decimal>(type: "numeric(18,8)", precision: 18, scale: 8, nullable: false),
Roi = table.Column<decimal>(type: "numeric(18,8)", precision: 18, scale: 8, nullable: false),
Volume = table.Column<decimal>(type: "numeric(18,8)", precision: 18, scale: 8, nullable: false),
Fees = table.Column<decimal>(type: "numeric(18,8)", precision: 18, scale: 8, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Bots", x => x.Identifier);
table.ForeignKey(
name: "FK_Bots_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "BundleBacktestRequests",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
RequestId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
UserName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
UserId = table.Column<int>(type: "integer", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
CompletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Status = table.Column<string>(type: "text", nullable: false),
BacktestRequestsJson = table.Column<string>(type: "text", nullable: false),
TotalBacktests = table.Column<int>(type: "integer", nullable: false),
CompletedBacktests = table.Column<int>(type: "integer", nullable: false),
FailedBacktests = table.Column<int>(type: "integer", nullable: false),
ErrorMessage = table.Column<string>(type: "text", nullable: true),
ProgressInfo = table.Column<string>(type: "text", nullable: true),
CurrentBacktest = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
EstimatedTimeRemainingSeconds = table.Column<int>(type: "integer", nullable: true),
Name = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
ResultsJson = table.Column<string>(type: "jsonb", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BundleBacktestRequests", x => x.Id);
table.ForeignKey(
name: "FK_BundleBacktestRequests_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "GeneticRequests",
columns: table => new
@@ -467,6 +502,33 @@ namespace Managing.Infrastructure.Databases.Migrations
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "MoneyManagements",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
Timeframe = table.Column<string>(type: "text", nullable: false),
StopLoss = table.Column<decimal>(type: "numeric(18,8)", nullable: false),
TakeProfit = table.Column<decimal>(type: "numeric(18,8)", nullable: false),
Leverage = table.Column<decimal>(type: "numeric(18,8)", nullable: false),
UserName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
UserId = table.Column<int>(type: "integer", nullable: true),
CreatedAt = 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_MoneyManagements", x => x.Id);
table.ForeignKey(
name: "FK_MoneyManagements_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateIndex(
name: "IX_Accounts_Key",
table: "Accounts",
@@ -540,30 +602,25 @@ namespace Managing.Infrastructure.Databases.Migrations
column: "WinRate");
migrationBuilder.CreateIndex(
name: "IX_BotBackups_CreateDate",
table: "BotBackups",
name: "IX_Bots_CreateDate",
table: "Bots",
column: "CreateDate");
migrationBuilder.CreateIndex(
name: "IX_BotBackups_Identifier",
table: "BotBackups",
name: "IX_Bots_Identifier",
table: "Bots",
column: "Identifier",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_BotBackups_LastStatus",
table: "BotBackups",
column: "LastStatus");
name: "IX_Bots_Status",
table: "Bots",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_BotBackups_UserName",
table: "BotBackups",
column: "UserName");
migrationBuilder.CreateIndex(
name: "IX_BotBackups_UserName_CreateDate",
table: "BotBackups",
columns: new[] { "UserName", "CreateDate" });
name: "IX_Bots_UserId",
table: "Bots",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_BundleBacktestRequests_CompletedAt",
@@ -586,6 +643,11 @@ namespace Managing.Infrastructure.Databases.Migrations
table: "BundleBacktestRequests",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_BundleBacktestRequests_UserId",
table: "BundleBacktestRequests",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_BundleBacktestRequests_UserName",
table: "BundleBacktestRequests",
@@ -596,17 +658,6 @@ namespace Managing.Infrastructure.Databases.Migrations
table: "BundleBacktestRequests",
columns: new[] { "UserName", "CreatedAt" });
migrationBuilder.CreateIndex(
name: "IX_Fees_Exchange",
table: "Fees",
column: "Exchange",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Fees_LastUpdate",
table: "Fees",
column: "LastUpdate");
migrationBuilder.CreateIndex(
name: "IX_FundingRates_Date",
table: "FundingRates",
@@ -694,6 +745,11 @@ namespace Managing.Infrastructure.Databases.Migrations
table: "MoneyManagements",
column: "Name");
migrationBuilder.CreateIndex(
name: "IX_MoneyManagements_UserId",
table: "MoneyManagements",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_MoneyManagements_UserName",
table: "MoneyManagements",
@@ -858,6 +914,28 @@ namespace Managing.Infrastructure.Databases.Migrations
table: "SpotlightOverviews",
column: "ScenarioCount");
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");
migrationBuilder.CreateIndex(
name: "IX_TopVolumeTickers_Date",
table: "TopVolumeTickers",
@@ -948,6 +1026,12 @@ namespace Managing.Infrastructure.Databases.Migrations
name: "IX_Trades_Status",
table: "Trades",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_Workers_WorkerType",
table: "Workers",
column: "WorkerType",
unique: true);
}
/// <inheritdoc />
@@ -960,14 +1044,11 @@ namespace Managing.Infrastructure.Databases.Migrations
name: "Backtests");
migrationBuilder.DropTable(
name: "BotBackups");
name: "Bots");
migrationBuilder.DropTable(
name: "BundleBacktestRequests");
migrationBuilder.DropTable(
name: "Fees");
migrationBuilder.DropTable(
name: "FundingRates");
@@ -989,12 +1070,21 @@ namespace Managing.Infrastructure.Databases.Migrations
migrationBuilder.DropTable(
name: "SpotlightOverviews");
migrationBuilder.DropTable(
name: "SynthMinersLeaderboards");
migrationBuilder.DropTable(
name: "SynthPredictions");
migrationBuilder.DropTable(
name: "TopVolumeTickers");
migrationBuilder.DropTable(
name: "Traders");
migrationBuilder.DropTable(
name: "Workers");
migrationBuilder.DropTable(
name: "Users");

View File

@@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Managing.Infrastructure.Databases.Migrations
{
[DbContext(typeof(ManagingDbContext))]
[Migration("20250725014014_AddUserIdToBundleBacktestRequest")]
partial class AddUserIdToBundleBacktestRequest
[Migration("20250801111224_UpdateUserEntity")]
partial class UpdateUserEntity
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -111,10 +111,6 @@ namespace Managing.Infrastructure.Databases.Migrations
.IsRequired()
.HasColumnType("jsonb");
b.Property<string>("OptimizedMoneyManagementJson")
.IsRequired()
.HasColumnType("jsonb");
b.Property<string>("PositionsJson")
.IsRequired()
.HasColumnType("jsonb");
@@ -181,50 +177,68 @@ namespace Managing.Infrastructure.Databases.Migrations
b.ToTable("Backtests");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b =>
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b =>
{
b.Property<int>("Id")
b.Property<Guid>("Identifier")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
.HasMaxLength(255)
.HasColumnType("uuid");
b.Property<DateTime>("CreateDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("text");
b.Property<decimal>("Fees")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<string>("Identifier")
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<int>("LastStatus")
b.Property<decimal>("Pnl")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<decimal>("Roi")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<DateTime>("StartupTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.Property<int>("TradeLosses")
.HasColumnType("integer");
b.Property<int>("TradeWins")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("UserName")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.Property<decimal>("Volume")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.HasKey("Identifier");
b.HasIndex("CreateDate");
b.HasIndex("Identifier")
.IsUnique();
b.HasIndex("LastStatus");
b.HasIndex("Status");
b.HasIndex("UserName");
b.HasIndex("UserId");
b.HasIndex("UserName", "CreateDate");
b.ToTable("BotBackups");
b.ToTable("Bots");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b =>
@@ -316,40 +330,6 @@ namespace Managing.Infrastructure.Databases.Migrations
b.ToTable("BundleBacktestRequests");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FeeEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Cost")
.HasColumnType("decimal(18,8)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Exchange")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("LastUpdate")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("Exchange")
.IsUnique();
b.HasIndex("LastUpdate");
b.ToTable("Fees");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b =>
{
b.Property<int>("Id")
@@ -630,6 +610,9 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("UserId")
.HasColumnType("integer");
b.Property<string>("UserName")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
@@ -638,6 +621,8 @@ namespace Managing.Infrastructure.Databases.Migrations
b.HasIndex("Name");
b.HasIndex("UserId");
b.HasIndex("UserName");
b.HasIndex("UserName", "Name");
@@ -647,11 +632,9 @@ namespace Managing.Infrastructure.Databases.Migrations
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b =>
{
b.Property<int>("Id")
b.Property<Guid>("Identifier")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
.HasColumnType("uuid");
b.Property<string>("AccountName")
.IsRequired()
@@ -664,11 +647,6 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<DateTime>("Date")
.HasColumnType("timestamp with time zone");
b.Property<string>("Identifier")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("Initiator")
.IsRequired()
.HasColumnType("text");
@@ -715,7 +693,7 @@ namespace Managing.Infrastructure.Databases.Migrations
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.HasKey("Id");
b.HasKey("Identifier");
b.HasIndex("CreatedAt");
@@ -1279,6 +1257,17 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
@@ -1299,6 +1288,16 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade")

View File

@@ -5,34 +5,36 @@
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class UpdateScoreMessageToText : Migration
public partial class UpdateUserEntity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "ScoreMessage",
table: "Backtests",
type: "text",
maxLength: 1000,
nullable: false,
name: "AgentName",
table: "Users",
type: "character varying(255)",
maxLength: 255,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(1000)",
oldMaxLength: 1000);
oldType: "character varying(255)",
oldMaxLength: 255);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "ScoreMessage",
table: "Backtests",
type: "character varying(1000)",
maxLength: 1000,
name: "AgentName",
table: "Users",
type: "character varying(255)",
maxLength: 255,
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "text",
oldMaxLength: 1000);
oldType: "character varying(255)",
oldMaxLength: 255,
oldNullable: true);
}
}
}

View File

@@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Managing.Infrastructure.Databases.Migrations
{
[DbContext(typeof(ManagingDbContext))]
[Migration("20250725172635_AddUserIdToMoneyManagement")]
partial class AddUserIdToMoneyManagement
[Migration("20250803201734_AddTickerToBots")]
partial class AddTickerToBots
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -177,50 +177,71 @@ namespace Managing.Infrastructure.Databases.Migrations
b.ToTable("Backtests");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b =>
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b =>
{
b.Property<int>("Id")
b.Property<Guid>("Identifier")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
.HasMaxLength(255)
.HasColumnType("uuid");
b.Property<DateTime>("CreateDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("text");
b.Property<decimal>("Fees")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<string>("Identifier")
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<int>("LastStatus")
b.Property<decimal>("Pnl")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<decimal>("Roi")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<DateTime>("StartupTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Ticker")
.HasColumnType("integer");
b.Property<int>("TradeLosses")
.HasColumnType("integer");
b.Property<int>("TradeWins")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("UserName")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.Property<decimal>("Volume")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.HasKey("Identifier");
b.HasIndex("CreateDate");
b.HasIndex("Identifier")
.IsUnique();
b.HasIndex("LastStatus");
b.HasIndex("Status");
b.HasIndex("UserName");
b.HasIndex("UserId");
b.HasIndex("UserName", "CreateDate");
b.ToTable("BotBackups");
b.ToTable("Bots");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b =>
@@ -312,40 +333,6 @@ namespace Managing.Infrastructure.Databases.Migrations
b.ToTable("BundleBacktestRequests");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FeeEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Cost")
.HasColumnType("decimal(18,8)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Exchange")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("LastUpdate")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("Exchange")
.IsUnique();
b.HasIndex("LastUpdate");
b.ToTable("Fees");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b =>
{
b.Property<int>("Id")
@@ -648,11 +635,9 @@ namespace Managing.Infrastructure.Databases.Migrations
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b =>
{
b.Property<int>("Id")
b.Property<Guid>("Identifier")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
.HasColumnType("uuid");
b.Property<string>("AccountName")
.IsRequired()
@@ -665,11 +650,6 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<DateTime>("Date")
.HasColumnType("timestamp with time zone");
b.Property<string>("Identifier")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("Initiator")
.IsRequired()
.HasColumnType("text");
@@ -716,7 +696,7 @@ namespace Managing.Infrastructure.Databases.Migrations
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.HasKey("Id");
b.HasKey("Identifier");
b.HasIndex("CreatedAt");
@@ -1280,6 +1260,17 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")

View File

@@ -5,25 +5,25 @@
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class RemoveOptimizedMoneyManagementJsonFromBacktestEntity : Migration
public partial class AddTickerToBots : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "OptimizedMoneyManagementJson",
table: "Backtests");
migrationBuilder.AddColumn<int>(
name: "Ticker",
table: "Bots",
type: "integer",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "OptimizedMoneyManagementJson",
table: "Backtests",
type: "jsonb",
nullable: false,
defaultValue: "");
migrationBuilder.DropColumn(
name: "Ticker",
table: "Bots");
}
}
}

View File

@@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Managing.Infrastructure.Databases.Migrations
{
[DbContext(typeof(ManagingDbContext))]
[Migration("20250725173315_AddUserIdToBotBackup")]
partial class AddUserIdToBotBackup
[Migration("20250803204725_UpdateBotTicker")]
partial class UpdateBotTicker
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -177,55 +177,71 @@ namespace Managing.Infrastructure.Databases.Migrations
b.ToTable("Backtests");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b =>
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b =>
{
b.Property<int>("Id")
b.Property<Guid>("Identifier")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
.HasMaxLength(255)
.HasColumnType("uuid");
b.Property<DateTime>("CreateDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("text");
b.Property<decimal>("Fees")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<string>("Identifier")
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<int>("LastStatus")
b.Property<decimal>("Pnl")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<decimal>("Roi")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<DateTime>("StartupTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Ticker")
.HasColumnType("integer");
b.Property<int>("TradeLosses")
.HasColumnType("integer");
b.Property<int>("TradeWins")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("UserId")
b.Property<int>("UserId")
.HasColumnType("integer");
b.Property<string>("UserName")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<decimal>("Volume")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.HasKey("Id");
b.HasKey("Identifier");
b.HasIndex("CreateDate");
b.HasIndex("Identifier")
.IsUnique();
b.HasIndex("LastStatus");
b.HasIndex("Status");
b.HasIndex("UserId");
b.HasIndex("UserName");
b.HasIndex("UserName", "CreateDate");
b.ToTable("BotBackups");
b.ToTable("Bots");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b =>
@@ -317,40 +333,6 @@ namespace Managing.Infrastructure.Databases.Migrations
b.ToTable("BundleBacktestRequests");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FeeEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Cost")
.HasColumnType("decimal(18,8)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Exchange")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("LastUpdate")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("Exchange")
.IsUnique();
b.HasIndex("LastUpdate");
b.ToTable("Fees");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b =>
{
b.Property<int>("Id")
@@ -653,11 +635,9 @@ namespace Managing.Infrastructure.Databases.Migrations
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b =>
{
b.Property<int>("Id")
b.Property<Guid>("Identifier")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
.HasColumnType("uuid");
b.Property<string>("AccountName")
.IsRequired()
@@ -670,11 +650,6 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<DateTime>("Date")
.HasColumnType("timestamp with time zone");
b.Property<string>("Identifier")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("Initiator")
.IsRequired()
.HasColumnType("text");
@@ -721,7 +696,7 @@ namespace Managing.Infrastructure.Databases.Migrations
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.HasKey("Id");
b.HasKey("Identifier");
b.HasIndex("CreatedAt");
@@ -1285,12 +1260,13 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b =>
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
.OnDelete(DeleteBehavior.SetNull)
.IsRequired();
b.Navigation("User");
});

View File

@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class UpdateBotTicker : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class AddAgentSummaryEntity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AgentSummaries",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column<int>(type: "integer", nullable: false),
AgentName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
TotalPnL = table.Column<decimal>(type: "numeric(18,8)", nullable: false),
TotalROI = table.Column<decimal>(type: "numeric(18,8)", nullable: false),
Wins = table.Column<int>(type: "integer", nullable: false),
Losses = table.Column<int>(type: "integer", nullable: false),
Runtime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedAt = 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_AgentSummaries", x => x.Id);
table.ForeignKey(
name: "FK_AgentSummaries_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AgentSummaries_AgentName",
table: "AgentSummaries",
column: "AgentName");
migrationBuilder.CreateIndex(
name: "IX_AgentSummaries_TotalPnL",
table: "AgentSummaries",
column: "TotalPnL");
migrationBuilder.CreateIndex(
name: "IX_AgentSummaries_UpdatedAt",
table: "AgentSummaries",
column: "UpdatedAt");
migrationBuilder.CreateIndex(
name: "IX_AgentSummaries_UserId",
table: "AgentSummaries",
column: "UserId",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AgentSummaries");
}
}
}

View File

@@ -0,0 +1,42 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class AddMissingAgentSummaryColumns : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ActiveStrategiesCount",
table: "AgentSummaries",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<decimal>(
name: "TotalVolume",
table: "AgentSummaries",
type: "numeric(18,8)",
precision: 18,
scale: 8,
nullable: false,
defaultValue: 0m);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ActiveStrategiesCount",
table: "AgentSummaries");
migrationBuilder.DropColumn(
name: "TotalVolume",
table: "AgentSummaries");
}
}
}

View File

@@ -66,6 +66,64 @@ namespace Managing.Infrastructure.Databases.Migrations
b.ToTable("Accounts");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AgentSummaryEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("ActiveStrategiesCount")
.HasColumnType("integer");
b.Property<string>("AgentName")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("Losses")
.HasColumnType("integer");
b.Property<DateTime?>("Runtime")
.HasColumnType("timestamp with time zone");
b.Property<decimal>("TotalPnL")
.HasColumnType("decimal(18,8)");
b.Property<decimal>("TotalROI")
.HasColumnType("decimal(18,8)");
b.Property<decimal>("TotalVolume")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("UserId")
.HasColumnType("integer");
b.Property<int>("Wins")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("AgentName");
b.HasIndex("TotalPnL");
b.HasIndex("UpdatedAt");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("AgentSummaries");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b =>
{
b.Property<int>("Id")
@@ -174,55 +232,71 @@ namespace Managing.Infrastructure.Databases.Migrations
b.ToTable("Backtests");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b =>
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b =>
{
b.Property<int>("Id")
b.Property<Guid>("Identifier")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
.HasMaxLength(255)
.HasColumnType("uuid");
b.Property<DateTime>("CreateDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("text");
b.Property<decimal>("Fees")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<string>("Identifier")
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<int>("LastStatus")
b.Property<decimal>("Pnl")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<decimal>("Roi")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.Property<DateTime>("StartupTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Ticker")
.HasColumnType("integer");
b.Property<int>("TradeLosses")
.HasColumnType("integer");
b.Property<int>("TradeWins")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int?>("UserId")
b.Property<int>("UserId")
.HasColumnType("integer");
b.Property<string>("UserName")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<decimal>("Volume")
.HasPrecision(18, 8)
.HasColumnType("numeric(18,8)");
b.HasKey("Id");
b.HasKey("Identifier");
b.HasIndex("CreateDate");
b.HasIndex("Identifier")
.IsUnique();
b.HasIndex("LastStatus");
b.HasIndex("Status");
b.HasIndex("UserId");
b.HasIndex("UserName");
b.HasIndex("UserName", "CreateDate");
b.ToTable("BotBackups");
b.ToTable("Bots");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b =>
@@ -616,11 +690,9 @@ namespace Managing.Infrastructure.Databases.Migrations
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b =>
{
b.Property<int>("Id")
b.Property<Guid>("Identifier")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
.HasColumnType("uuid");
b.Property<string>("AccountName")
.IsRequired()
@@ -633,11 +705,6 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<DateTime>("Date")
.HasColumnType("timestamp with time zone");
b.Property<string>("Identifier")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("Initiator")
.IsRequired()
.HasColumnType("text");
@@ -684,7 +751,7 @@ namespace Managing.Infrastructure.Databases.Migrations
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.HasKey("Id");
b.HasKey("Identifier");
b.HasIndex("CreatedAt");
@@ -1248,12 +1315,24 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotBackupEntity", b =>
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AgentSummaryEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.IsRequired();
b.Navigation("User");
});

View File

@@ -0,0 +1,232 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Statistics;
using Managing.Infrastructure.Databases.PostgreSql;
using Managing.Infrastructure.Databases.PostgreSql.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Database.PostgreSql;
public class AgentSummaryRepository : IAgentSummaryRepository
{
private readonly ManagingDbContext _context;
private readonly ILogger<AgentSummaryRepository> _logger;
public AgentSummaryRepository(ManagingDbContext context, ILogger<AgentSummaryRepository> logger)
{
_context = context;
_logger = logger;
}
public async Task<AgentSummary?> GetByUserIdAsync(int userId)
{
var entity = await _context.AgentSummaries
.Include(a => a.User)
.FirstOrDefaultAsync(a => a.UserId == userId);
return entity != null ? MapToDomain(entity) : null;
}
public async Task<AgentSummary?> GetByAgentNameAsync(string agentName)
{
var entity = await _context.AgentSummaries
.Include(a => a.User)
.FirstOrDefaultAsync(a => a.AgentName == agentName);
return entity != null ? MapToDomain(entity) : null;
}
public async Task<IEnumerable<AgentSummary>> GetAllAsync()
{
var entities = await _context.AgentSummaries
.Include(a => a.User)
.ToListAsync();
return entities.Select(MapToDomain);
}
public async Task InsertAsync(AgentSummary agentSummary)
{
var entity = MapToEntity(agentSummary);
entity.CreatedAt = DateTime.UtcNow;
entity.UpdatedAt = DateTime.UtcNow;
await _context.AgentSummaries.AddAsync(entity);
await _context.SaveChangesAsync();
_logger.LogInformation("AgentSummary inserted for user {UserId} with agent name {AgentName}",
agentSummary.UserId, agentSummary.AgentName);
}
public async Task UpdateAsync(AgentSummary agentSummary)
{
var entity = await _context.AgentSummaries
.FirstOrDefaultAsync(a => a.UserId == agentSummary.UserId);
if (entity != null)
{
MapToEntity(agentSummary, entity);
entity.UpdatedAt = DateTime.UtcNow;
// No need to call Update() since the entity is already being tracked
await _context.SaveChangesAsync();
_logger.LogInformation("AgentSummary updated for user {UserId} with agent name {AgentName}",
agentSummary.UserId, agentSummary.AgentName);
}
}
public async Task SaveOrUpdateAsync(AgentSummary agentSummary)
{
// Ensure the User entity exists and is saved
if (agentSummary.User != null)
{
var existingUser = await _context.Users
.FirstOrDefaultAsync(u => u.Id == agentSummary.UserId);
if (existingUser == null)
{
// User doesn't exist, save it first
var userEntity = PostgreSqlMappers.Map(agentSummary.User);
await _context.Users.AddAsync(userEntity);
await _context.SaveChangesAsync();
_logger.LogInformation("User created for AgentSummary with ID {UserId}", agentSummary.UserId);
}
}
var existing = await _context.AgentSummaries
.FirstOrDefaultAsync(a => a.UserId == agentSummary.UserId);
if (existing == null)
{
await InsertAsync(agentSummary);
}
else
{
// Update existing record - modify the tracked entity directly
MapToEntity(agentSummary, existing);
existing.UpdatedAt = DateTime.UtcNow;
// No need to call Update() since the entity is already being tracked
await _context.SaveChangesAsync();
_logger.LogInformation("AgentSummary updated for user {UserId} with agent name {AgentName}",
agentSummary.UserId, agentSummary.AgentName);
}
}
public async Task<(IEnumerable<AgentSummary> Results, int TotalCount)> GetPaginatedAsync(
int page,
int pageSize,
SortableFields sortBy,
string sortOrder,
IEnumerable<string>? agentNames = null)
{
// Start with base query
var query = _context.AgentSummaries.Include(a => a.User).AsQueryable();
// Apply agent name filtering if specified
if (agentNames != null && agentNames.Any())
{
query = query.Where(a => agentNames.Contains(a.AgentName));
}
// Get total count before applying pagination
var totalCount = await query.CountAsync();
// Apply sorting
var isDescending = sortOrder.ToLowerInvariant() == "desc";
query = sortBy switch
{
SortableFields.TotalPnL => isDescending
? query.OrderByDescending(a => a.TotalPnL)
: query.OrderBy(a => a.TotalPnL),
SortableFields.TotalROI => isDescending
? query.OrderByDescending(a => a.TotalROI)
: query.OrderBy(a => a.TotalROI),
SortableFields.Wins => isDescending
? query.OrderByDescending(a => a.Wins)
: query.OrderBy(a => a.Wins),
SortableFields.Losses => isDescending
? query.OrderByDescending(a => a.Losses)
: query.OrderBy(a => a.Losses),
SortableFields.AgentName => isDescending
? query.OrderByDescending(a => a.AgentName)
: query.OrderBy(a => a.AgentName),
SortableFields.CreatedAt => isDescending
? query.OrderByDescending(a => a.CreatedAt)
: query.OrderBy(a => a.CreatedAt),
SortableFields.UpdatedAt => isDescending
? query.OrderByDescending(a => a.UpdatedAt)
: query.OrderBy(a => a.UpdatedAt),
_ => isDescending
? query.OrderByDescending(a => a.TotalPnL) // Default to TotalPnL desc
: query.OrderBy(a => a.TotalPnL)
};
// Apply pagination
var results = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
// Map to domain objects
var domainResults = results.Select(MapToDomain);
return (domainResults, totalCount);
}
private static AgentSummaryEntity MapToEntity(AgentSummary domain)
{
return new AgentSummaryEntity
{
Id = domain.Id,
UserId = domain.UserId,
AgentName = domain.AgentName,
TotalPnL = domain.TotalPnL,
TotalROI = domain.TotalROI,
Wins = domain.Wins,
Losses = domain.Losses,
Runtime = domain.Runtime,
CreatedAt = domain.CreatedAt,
UpdatedAt = domain.UpdatedAt,
ActiveStrategiesCount = domain.ActiveStrategiesCount,
TotalVolume = domain.TotalVolume
};
}
private static void MapToEntity(AgentSummary domain, AgentSummaryEntity entity)
{
entity.UserId = domain.UserId;
entity.AgentName = domain.AgentName;
entity.TotalPnL = domain.TotalPnL;
entity.TotalROI = domain.TotalROI;
entity.Wins = domain.Wins;
entity.Losses = domain.Losses;
entity.Runtime = domain.Runtime;
entity.ActiveStrategiesCount = domain.ActiveStrategiesCount;
entity.TotalVolume = domain.TotalVolume;
}
private static AgentSummary MapToDomain(AgentSummaryEntity entity)
{
return new AgentSummary
{
Id = entity.Id,
UserId = entity.UserId,
AgentName = entity.AgentName,
TotalPnL = entity.TotalPnL,
TotalROI = entity.TotalROI,
Wins = entity.Wins,
Losses = entity.Losses,
Runtime = entity.Runtime,
CreatedAt = entity.CreatedAt,
UpdatedAt = entity.UpdatedAt,
ActiveStrategiesCount = entity.ActiveStrategiesCount,
TotalVolume = entity.TotalVolume,
User = PostgreSqlMappers.Map(entity.User)
};
}
}

View File

@@ -0,0 +1,20 @@
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
public class AgentSummaryEntity
{
public int Id { get; set; }
public int UserId { get; set; }
public string AgentName { get; set; }
public decimal TotalPnL { get; set; }
public decimal TotalROI { get; set; }
public int Wins { get; set; }
public int Losses { get; set; }
public DateTime? Runtime { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public int ActiveStrategiesCount { get; set; }
public decimal TotalVolume { get; set; }
// Navigation property
public UserEntity User { get; set; }
}

View File

@@ -1,36 +0,0 @@
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,30 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("Bots")]
public class BotEntity
{
[Key] public Guid Identifier { get; set; }
[Required] [MaxLength(255)] public required string Name { get; set; }
public Ticker Ticker { get; set; }
public int UserId { get; set; }
[Required] [ForeignKey("UserId")] public required UserEntity User { get; set; }
public BotStatus Status { get; set; }
public DateTime CreateDate { get; set; }
public DateTime UpdatedAt { get; set; }
public DateTime StartupTime { get; set; }
public int TradeWins { get; set; }
public int TradeLosses { get; set; }
public decimal Pnl { get; set; }
public decimal Roi { get; set; }
public decimal Volume { get; set; }
public decimal Fees { get; set; }
}

View File

@@ -7,55 +7,41 @@ 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; }
[Key] [Required] public Guid Identifier { get; set; }
public DateTime Date { get; set; }
[Column(TypeName = "decimal(18,8)")]
public decimal ProfitAndLoss { 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; }
[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; }
[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; }
}
[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

@@ -1,10 +1,14 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("Users")]
public class UserEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string? AgentName { get; set; }
[Key] public int Id { get; set; }
[Required] [MaxLength(255)] public required string Name { get; set; }
[MaxLength(255)] public string? AgentName { get; set; }
public string? AvatarUrl { get; set; }
public string? TelegramChannel { get; set; }
}
}

View File

@@ -29,9 +29,10 @@ public class ManagingDbContext : DbContext
public DbSet<SpotlightOverviewEntity> SpotlightOverviews { get; set; }
public DbSet<TraderEntity> Traders { get; set; }
public DbSet<FundingRateEntity> FundingRates { get; set; }
public DbSet<AgentSummaryEntity> AgentSummaries { get; set; }
// Bot entities
public DbSet<BotBackupEntity> BotBackups { get; set; }
public DbSet<BotEntity> Bots { get; set; }
// Settings entities
public DbSet<MoneyManagementEntity> MoneyManagements { get; set; }
@@ -46,6 +47,10 @@ public class ManagingDbContext : DbContext
{
base.OnModelCreating(modelBuilder);
// Configure schema for Orleans tables (if needed for future organization)
// Orleans tables will remain in the default schema for now
// This can be changed later if needed by configuring specific schemas
// Configure Account entity
modelBuilder.Entity<AccountEntity>(entity =>
{
@@ -280,8 +285,7 @@ public class ManagingDbContext : DbContext
// Configure Position entity
modelBuilder.Entity<PositionEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255);
entity.HasKey(e => e.Identifier);
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>();
@@ -348,7 +352,6 @@ public class ManagingDbContext : DbContext
});
// Configure TopVolumeTicker entity
modelBuilder.Entity<TopVolumeTickerEntity>(entity =>
{
@@ -425,22 +428,26 @@ public class ManagingDbContext : DbContext
});
// Configure BotBackup entity
modelBuilder.Entity<BotBackupEntity>(entity =>
modelBuilder.Entity<BotEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasKey(e => e.Identifier);
entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255);
entity.Property(e => e.UserName).HasMaxLength(255);
entity.Property(e => e.Data).IsRequired().HasColumnType("text");
entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
entity.Property(e => e.Status).IsRequired().HasConversion<string>();
entity.Property(e => e.CreateDate).IsRequired();
entity.Property(e => e.StartupTime).IsRequired();
entity.Property(e => e.TradeWins).IsRequired();
entity.Property(e => e.TradeLosses).IsRequired();
entity.Property(e => e.Pnl).HasPrecision(18, 8);
entity.Property(e => e.Roi).HasPrecision(18, 8);
entity.Property(e => e.Volume).HasPrecision(18, 8);
entity.Property(e => e.Fees).HasPrecision(18, 8);
// Create indexes
entity.HasIndex(e => e.Identifier).IsUnique();
entity.HasIndex(e => e.UserName);
entity.HasIndex(e => e.LastStatus);
entity.HasIndex(e => e.Status);
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()
@@ -517,5 +524,105 @@ public class ManagingDbContext : DbContext
entity.HasIndex(e => e.CacheKey).IsUnique();
entity.HasIndex(e => e.CreatedAt);
});
// Configure AgentSummary entity
modelBuilder.Entity<AgentSummaryEntity>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.UserId).IsRequired();
entity.Property(e => e.AgentName).IsRequired().HasMaxLength(255);
entity.Property(e => e.TotalPnL).HasColumnType("decimal(18,8)");
entity.Property(e => e.TotalROI).HasColumnType("decimal(18,8)");
entity.Property(e => e.Wins).IsRequired();
entity.Property(e => e.Losses).IsRequired();
entity.Property(e => e.Runtime);
entity.Property(e => e.CreatedAt).IsRequired();
entity.Property(e => e.UpdatedAt).IsRequired();
entity.Property(e => e.ActiveStrategiesCount).IsRequired();
entity.Property(e => e.TotalVolume).HasPrecision(18, 8);
// Create indexes for common queries
entity.HasIndex(e => e.UserId).IsUnique();
entity.HasIndex(e => e.AgentName);
entity.HasIndex(e => e.TotalPnL);
entity.HasIndex(e => e.UpdatedAt);
// Configure relationship with User
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.Cascade);
});
}
/// <summary>
/// Ensures Orleans tables are properly initialized in the database.
/// This method can be called during application startup to verify Orleans infrastructure.
/// </summary>
public async Task EnsureOrleansTablesExistAsync()
{
// Orleans tables are automatically created by the Orleans framework
// when using AdoNetClustering and AdoNetReminderService.
// This method serves as a verification point and can be extended
// for custom Orleans table management if needed.
// For now, we just ensure the database is accessible
await Database.CanConnectAsync();
}
/// <summary>
/// Gets Orleans table statistics for monitoring purposes.
/// This helps track Orleans table sizes and performance.
/// </summary>
public async Task<Dictionary<string, long>> GetOrleansTableStatsAsync()
{
var stats = new Dictionary<string, long>();
// Orleans table names
var orleansTables = new[]
{
"orleansmembershiptable",
"orleansmembershipversiontable",
"orleansquery",
"orleansreminderstable",
"orleansstorage"
};
foreach (var tableName in orleansTables)
{
try
{
var count = await Database.SqlQueryRaw<long>($"SELECT COUNT(*) FROM {tableName}").FirstOrDefaultAsync();
stats[tableName] = count;
}
catch
{
// Table might not exist yet (normal during startup)
stats[tableName] = -1;
}
}
return stats;
}
/// <summary>
/// Database organization strategy:
/// - Application tables: Default schema (public)
/// - Orleans tables: Default schema (public) - managed by Orleans framework
/// - Future consideration: Move Orleans tables to 'orleans' schema if needed
///
/// Benefits of current approach:
/// - Single database simplifies deployment and backup
/// - Orleans tables are automatically managed by the framework
/// - No additional configuration complexity
/// - Easier monitoring and maintenance
/// </summary>
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
// Add any additional configuration here if needed
}
}

View File

@@ -1,6 +1,7 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Bots;
using Microsoft.EntityFrameworkCore;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql;
@@ -13,7 +14,7 @@ public class PostgreSqlBotRepository : IBotRepository
_context = context;
}
public async Task InsertBotAsync(BotBackup bot)
public async Task InsertBotAsync(Bot bot)
{
bot.CreateDate = DateTime.UtcNow;
var entity = PostgreSqlMappers.Map(bot);
@@ -22,18 +23,24 @@ public class PostgreSqlBotRepository : IBotRepository
{
var userEntity = await _context.Users
.AsNoTracking()
.FirstOrDefaultAsync(u => u.Name == bot.User.Name)
.FirstOrDefaultAsync(u => u.Id == bot.User.Id)
.ConfigureAwait(false);
entity.UserId = userEntity?.Id;
if (userEntity == null)
{
throw new InvalidOperationException($"User with id '{bot.User.Id}' not found");
}
entity.UserId = userEntity.Id;
}
await _context.BotBackups.AddAsync(entity).ConfigureAwait(false);
await _context.Bots.AddAsync(entity).ConfigureAwait(false);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
public async Task<IEnumerable<BotBackup>> GetBotsAsync()
public async Task<IEnumerable<Bot>> GetBotsAsync()
{
var entities = await _context.BotBackups
var entities = await _context.Bots
.AsNoTracking()
.Include(m => m.User)
.ToListAsync()
@@ -42,9 +49,9 @@ public class PostgreSqlBotRepository : IBotRepository
return PostgreSqlMappers.Map(entities);
}
public async Task UpdateBackupBot(BotBackup bot)
public async Task UpdateBot(Bot bot)
{
var existingEntity = await _context.BotBackups
var existingEntity = await _context.Bots
.AsTracking()
.FirstOrDefaultAsync(b => b.Identifier == bot.Identifier)
.ConfigureAwait(false);
@@ -54,18 +61,25 @@ public class PostgreSqlBotRepository : IBotRepository
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;
// Update the existing entity properties directly instead of creating a new one
existingEntity.Name = bot.Name;
existingEntity.Ticker = bot.Ticker;
existingEntity.Status = bot.Status;
existingEntity.StartupTime = bot.StartupTime;
existingEntity.TradeWins = bot.TradeWins;
existingEntity.TradeLosses = bot.TradeLosses;
existingEntity.Pnl = bot.Pnl;
existingEntity.Roi = bot.Roi;
existingEntity.Volume = bot.Volume;
existingEntity.Fees = bot.Fees;
existingEntity.UpdatedAt = DateTime.UtcNow;
existingEntity.UserName = bot.User?.Name;
await _context.SaveChangesAsync().ConfigureAwait(false);
}
public async Task DeleteBotBackup(string identifier)
public async Task DeleteBot(Guid identifier)
{
var entity = await _context.BotBackups
var entity = await _context.Bots
.AsTracking()
.FirstOrDefaultAsync(b => b.Identifier == identifier)
.ConfigureAwait(false);
@@ -75,17 +89,142 @@ public class PostgreSqlBotRepository : IBotRepository
throw new InvalidOperationException($"Bot backup with identifier '{identifier}' not found");
}
_context.BotBackups.Remove(entity);
_context.Bots.Remove(entity);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
public async Task<BotBackup?> GetBotByIdentifierAsync(string identifier)
public async Task<Bot> GetBotByIdentifierAsync(Guid identifier)
{
var entity = await _context.BotBackups
var entity = await _context.Bots
.AsNoTracking()
.Include(m => m.User)
.FirstOrDefaultAsync(b => b.Identifier == identifier)
.ConfigureAwait(false);
return PostgreSqlMappers.Map(entity);
}
public async Task<IEnumerable<Bot>> GetBotsByUserIdAsync(int id)
{
var entities = await _context.Bots
.AsNoTracking()
.Include(m => m.User)
.Where(b => b.UserId == id)
.ToListAsync()
.ConfigureAwait(false);
return PostgreSqlMappers.Map(entities);
}
public async Task<IEnumerable<Bot>> GetBotsByStatusAsync(BotStatus status)
{
var entities = await _context.Bots
.AsNoTracking()
.Include(m => m.User)
.Where(b => b.Status == status)
.ToListAsync()
.ConfigureAwait(false);
return PostgreSqlMappers.Map(entities);
}
public async Task<Bot> GetBotByNameAsync(string name)
{
var entity = await _context.Bots
.AsNoTracking()
.Include(m => m.User)
.FirstOrDefaultAsync(b => b.Name == name)
.ConfigureAwait(false);
return PostgreSqlMappers.Map(entity);
}
public async Task<IEnumerable<Bot>> GetBotsByIdsAsync(IEnumerable<Guid> identifiers)
{
var entities = await _context.Bots
.AsNoTracking()
.Include(m => m.User)
.Where(b => identifiers.Contains(b.Identifier))
.ToListAsync()
.ConfigureAwait(false);
return PostgreSqlMappers.Map(entities);
}
public async Task<(IEnumerable<Bot> Bots, int TotalCount)> GetBotsPaginatedAsync(
int pageNumber,
int pageSize,
BotStatus? status = null,
string? name = null,
string? ticker = null,
string? agentName = null,
string sortBy = "CreateDate",
string sortDirection = "Desc")
{
// Build the query with filters
var query = _context.Bots
.AsNoTracking()
.Include(m => m.User)
.AsQueryable();
// Apply filters
if (status.HasValue)
{
query = query.Where(b => b.Status == status.Value);
}
if (!string.IsNullOrWhiteSpace(name))
{
query = query.Where(b => EF.Functions.ILike(b.Name, $"%{name}%"));
}
if (!string.IsNullOrWhiteSpace(ticker))
{
query = query.Where(b => EF.Functions.ILike(b.Ticker.ToString(), $"%{ticker}%"));
}
if (!string.IsNullOrWhiteSpace(agentName))
{
query = query.Where(b => b.User != null && EF.Functions.ILike(b.User.AgentName, $"%{agentName}%"));
}
// Get total count before applying pagination
var totalCount = await query.CountAsync().ConfigureAwait(false);
// Apply sorting
query = sortBy.ToLower() switch
{
"name" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.Name)
: query.OrderByDescending(b => b.Name),
"ticker" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.Ticker)
: query.OrderByDescending(b => b.Ticker),
"status" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.Status)
: query.OrderByDescending(b => b.Status),
"startuptime" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.StartupTime)
: query.OrderByDescending(b => b.StartupTime),
"pnl" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.Pnl)
: query.OrderByDescending(b => b.Pnl),
"winrate" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.TradeWins / (b.TradeWins + b.TradeLosses))
: query.OrderByDescending(b => b.TradeWins / (b.TradeWins + b.TradeLosses)),
"agentname" => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.User.AgentName)
: query.OrderByDescending(b => b.User.AgentName),
_ => sortDirection.ToLower() == "asc"
? query.OrderBy(b => b.CreateDate)
: query.OrderByDescending(b => b.CreateDate) // Default to CreateDate
};
// Apply pagination
var skip = (pageNumber - 1) * pageSize;
var entities = await query
.Skip(skip)
.Take(pageSize)
.ToListAsync()
.ConfigureAwait(false);
var bots = PostgreSqlMappers.Map(entities);
return (bots, totalCount);
}
}

View File

@@ -3,6 +3,7 @@ using Managing.Domain.Accounts;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Indicators;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Statistics;
@@ -13,7 +14,6 @@ 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;
@@ -125,7 +125,8 @@ public static class PostgreSqlMappers
Name = entity.Name,
AgentName = entity.AgentName,
AvatarUrl = entity.AvatarUrl,
TelegramChannel = entity.TelegramChannel
TelegramChannel = entity.TelegramChannel,
Id = entity.Id // Assuming Id is the primary key for UserEntity
};
}
@@ -183,7 +184,9 @@ public static class PostgreSqlMappers
{
try
{
geneticRequest.EligibleIndicators = SystemJsonSerializer.Deserialize<List<IndicatorType>>(entity.EligibleIndicatorsJson) ?? new List<IndicatorType>();
geneticRequest.EligibleIndicators =
SystemJsonSerializer.Deserialize<List<IndicatorType>>(entity.EligibleIndicatorsJson) ??
new List<IndicatorType>();
}
catch
{
@@ -263,10 +266,12 @@ public static class PostgreSqlMappers
// 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)
var positionsList = JsonConvert.DeserializeObject<List<Position>>(entity.PositionsJson) ?? new List<Position>();
var positions = positionsList.ToDictionary(p => p.Identifier, p => p);
var signalsList = JsonConvert.DeserializeObject<List<LightSignal>>(entity.SignalsJson) ?? new List<LightSignal>();
var signals = signalsList.ToDictionary(s => s.Identifier, s => s);
var statistics = !string.IsNullOrEmpty(entity.StatisticsJson)
? JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson)
: null;
var backtest = new Backtest(config, positions, signals)
@@ -303,8 +308,8 @@ public static class PostgreSqlMappers
GrowthPercentage = backtest.GrowthPercentage,
HodlPercentage = backtest.HodlPercentage,
ConfigJson = JsonConvert.SerializeObject(backtest.Config),
PositionsJson = JsonConvert.SerializeObject(backtest.Positions),
SignalsJson = JsonConvert.SerializeObject(backtest.Signals),
PositionsJson = JsonConvert.SerializeObject(backtest.Positions.Values.ToList()),
SignalsJson = JsonConvert.SerializeObject(backtest.Signals.Values.ToList()),
StartDate = backtest.StartDate,
EndDate = backtest.EndDate,
MoneyManagementJson = JsonConvert.SerializeObject(backtest.Config?.MoneyManagement),
@@ -354,7 +359,8 @@ public static class PostgreSqlMappers
{
try
{
bundleRequest.Results = JsonConvert.DeserializeObject<List<string>>(entity.ResultsJson) ?? new List<string>();
bundleRequest.Results = JsonConvert.DeserializeObject<List<string>>(entity.ResultsJson) ??
new List<string>();
}
catch
{
@@ -426,7 +432,7 @@ public static class PostgreSqlMappers
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
Indicators = new List<IndicatorBase>() // Will be populated separately when needed
};
}
@@ -443,11 +449,11 @@ public static class PostgreSqlMappers
}
// Indicator mappings
public static Indicator Map(IndicatorEntity entity)
public static IndicatorBase Map(IndicatorEntity entity)
{
if (entity == null) return null;
return new Indicator(entity.Name, entity.Type)
return new IndicatorBase(entity.Name, entity.Type)
{
SignalType = entity.SignalType,
MinimumHistory = entity.MinimumHistory,
@@ -463,26 +469,26 @@ public static class PostgreSqlMappers
};
}
public static IndicatorEntity Map(Indicator indicator)
public static IndicatorEntity Map(IndicatorBase indicatorBase)
{
if (indicator == null) return null;
if (indicatorBase == null) return null;
return new IndicatorEntity
{
Name = indicator.Name,
Type = indicator.Type,
Name = indicatorBase.Name,
Type = indicatorBase.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
SignalType = indicatorBase.SignalType,
MinimumHistory = indicatorBase.MinimumHistory,
Period = indicatorBase.Period,
FastPeriods = indicatorBase.FastPeriods,
SlowPeriods = indicatorBase.SlowPeriods,
SignalPeriods = indicatorBase.SignalPeriods,
Multiplier = indicatorBase.Multiplier,
SmoothPeriods = indicatorBase.SmoothPeriods,
StochPeriods = indicatorBase.StochPeriods,
CyclePeriods = indicatorBase.CyclePeriods,
UserName = indicatorBase.User?.Name
};
}
@@ -491,8 +497,8 @@ public static class PostgreSqlMappers
{
if (entity == null) return null;
var candle = !string.IsNullOrEmpty(entity.CandleJson)
? JsonConvert.DeserializeObject<Candle>(entity.CandleJson)
var candle = !string.IsNullOrEmpty(entity.CandleJson)
? JsonConvert.DeserializeObject<Candle>(entity.CandleJson)
: null;
return new Signal(
@@ -541,7 +547,8 @@ public static class PostgreSqlMappers
var moneyManagement = new MoneyManagement(); // Default money management
if (!string.IsNullOrEmpty(entity.MoneyManagementJson))
{
moneyManagement = JsonConvert.DeserializeObject<MoneyManagement>(entity.MoneyManagementJson) ?? new MoneyManagement();
moneyManagement = JsonConvert.DeserializeObject<MoneyManagement>(entity.MoneyManagementJson) ??
new MoneyManagement();
}
var position = new Position(
@@ -590,7 +597,9 @@ public static class PostgreSqlMappers
SignalIdentifier = position.SignalIdentifier,
AccountName = position.AccountName,
UserName = position.User?.Name,
MoneyManagementJson = position.MoneyManagement != null ? JsonConvert.SerializeObject(position.MoneyManagement) : null
MoneyManagementJson = position.MoneyManagement != null
? JsonConvert.SerializeObject(position.MoneyManagement)
: null
};
}
@@ -636,16 +645,15 @@ public static class PostgreSqlMappers
}
// 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)
public static IEnumerable<IndicatorBase> Map(IEnumerable<IndicatorEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<Indicator>();
return entities?.Select(Map) ?? Enumerable.Empty<IndicatorBase>();
}
public static IEnumerable<Signal> Map(IEnumerable<SignalEntity> entities)
@@ -663,48 +671,57 @@ public static class PostgreSqlMappers
#region Bot Mappings
// BotBackup mappings
public static BotBackup Map(BotBackupEntity entity)
public static Bot Map(BotEntity entity)
{
if (entity == null) return null;
var botBackup = new BotBackup
var bot = new Bot
{
Identifier = entity.Identifier,
User = entity.User != null ? Map(entity.User) : null,
LastStatus = entity.LastStatus,
CreateDate = entity.CreateDate
Status = entity.Status,
CreateDate = entity.CreateDate,
Name = entity.Name,
Ticker = entity.Ticker,
StartupTime = entity.StartupTime,
TradeWins = entity.TradeWins,
TradeLosses = entity.TradeLosses,
Pnl = entity.Pnl,
Roi = entity.Roi,
Volume = entity.Volume,
Fees = entity.Fees
};
// Deserialize the JSON data using the helper method
botBackup.DeserializeData(entity.Data);
return botBackup;
return bot;
}
public static BotBackupEntity Map(BotBackup botBackup)
public static BotEntity Map(Bot bot)
{
if (botBackup == null) return null;
if (bot == null) return null;
return new BotBackupEntity
return new BotEntity
{
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,
Identifier = bot.Identifier,
UserId = bot.User.Id,
User = bot.User != null ? Map(bot.User) : null,
Status = bot.Status,
CreateDate = bot.CreateDate,
Name = bot.Name,
Ticker = bot.Ticker,
StartupTime = bot.StartupTime,
TradeWins = bot.TradeWins,
TradeLosses = bot.TradeLosses,
Pnl = bot.Pnl,
Roi = bot.Roi,
Volume = bot.Volume,
Fees = bot.Fees,
UpdatedAt = DateTime.UtcNow
};
}
public static IEnumerable<BotBackup> Map(IEnumerable<BotBackupEntity> entities)
public static IEnumerable<Bot> Map(IEnumerable<BotEntity> entities)
{
return entities?.Select(Map) ?? Enumerable.Empty<BotBackup>();
}
public static IEnumerable<BotBackupEntity> Map(IEnumerable<BotBackup> botBackups)
{
return botBackups?.Select(Map) ?? Enumerable.Empty<BotBackupEntity>();
return entities?.Select(Map) ?? Enumerable.Empty<Bot>();
}
#endregion
@@ -763,7 +780,8 @@ public static class PostgreSqlMappers
{
try
{
overview.Spotlights = SystemJsonSerializer.Deserialize<List<Spotlight>>(entity.SpotlightsJson) ?? new List<Spotlight>();
overview.Spotlights = SystemJsonSerializer.Deserialize<List<Spotlight>>(entity.SpotlightsJson) ??
new List<Spotlight>();
}
catch (JsonException)
{
@@ -913,4 +931,4 @@ public static class PostgreSqlMappers
}
#endregion
}
}

View File

@@ -86,8 +86,8 @@ public class PostgreSqlTradingRepository : ITradingRepository
var existingScenario = await _context.Scenarios
.AsNoTracking()
.FirstOrDefaultAsync(s => s.Name == scenario.Name &&
((scenario.User == null && s.UserName == null) ||
(scenario.User != null && s.UserName == scenario.User.Name)));
((scenario.User == null && s.UserName == null) ||
(scenario.User != null && s.UserName == scenario.User.Name)));
if (existingScenario != null)
{
@@ -107,8 +107,8 @@ public class PostgreSqlTradingRepository : ITradingRepository
var indicatorEntity = await _context.Indicators
.AsNoTracking()
.FirstOrDefaultAsync(i => i.Name == indicator.Name &&
((indicator.User == null && i.UserName == null) ||
(indicator.User != null && i.UserName == indicator.User.Name)));
((indicator.User == null && i.UserName == null) ||
(indicator.User != null && i.UserName == indicator.User.Name)));
if (indicatorEntity != null)
{
@@ -120,6 +120,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
_context.ScenarioIndicators.Add(junction);
}
}
await _context.SaveChangesAsync();
}
}
@@ -135,7 +136,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
entity.LoopbackPeriod = scenario.LoopbackPeriod ?? 1;
entity.UserName = scenario.User?.Name;
entity.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
}
}
@@ -149,7 +150,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
var indicator = _context.Indicators
.AsTracking()
.FirstOrDefault(i => i.Name == name);
if (indicator != null)
{
_context.Indicators.Remove(indicator);
@@ -164,7 +165,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
await _context.SaveChangesAsync();
}
public async Task<IEnumerable<Indicator>> GetIndicatorsAsync()
public async Task<IEnumerable<IndicatorBase>> GetIndicatorsAsync()
{
var indicators = await _context.Indicators
.AsNoTracking()
@@ -174,7 +175,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
return PostgreSqlMappers.Map(indicators);
}
public async Task<IEnumerable<Indicator>> GetStrategiesAsync()
public async Task<IEnumerable<IndicatorBase>> GetStrategiesAsync()
{
var indicators = await _context.Indicators
.AsNoTracking()
@@ -183,7 +184,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
return PostgreSqlMappers.Map(indicators);
}
public async Task<Indicator> GetStrategyByNameAsync(string name)
public async Task<IndicatorBase> GetStrategyByNameAsync(string name)
{
var indicator = await _context.Indicators
.AsNoTracking()
@@ -193,48 +194,48 @@ public class PostgreSqlTradingRepository : ITradingRepository
return PostgreSqlMappers.Map(indicator);
}
public async Task InsertStrategyAsync(Indicator indicator)
public async Task InsertIndicatorAsync(IndicatorBase indicatorBase)
{
// Check if indicator already exists for the same user
var existingIndicator = await _context.Indicators
.AsNoTracking()
.FirstOrDefaultAsync(i => i.Name == indicator.Name &&
((indicator.User == null && i.UserName == null) ||
(indicator.User != null && i.UserName == indicator.User.Name)));
.FirstOrDefaultAsync(i => i.Name == indicatorBase.Name &&
((indicatorBase.User == null && i.UserName == null) ||
(indicatorBase.User != null && i.UserName == indicatorBase.User.Name)));
if (existingIndicator != null)
{
throw new InvalidOperationException(
$"Indicator with name '{indicator.Name}' already exists for user '{indicator.User?.Name}'");
$"Indicator with name '{indicatorBase.Name}' already exists for user '{indicatorBase.User?.Name}'");
}
var entity = PostgreSqlMappers.Map(indicator);
var entity = PostgreSqlMappers.Map(indicatorBase);
_context.Indicators.Add(entity);
await _context.SaveChangesAsync();
}
public async Task UpdateStrategyAsync(Indicator indicator)
public async Task UpdateStrategyAsync(IndicatorBase indicatorBase)
{
var entity = _context.Indicators
.AsTracking()
.FirstOrDefault(i => i.Name == indicator.Name);
.FirstOrDefault(i => i.Name == indicatorBase.Name);
if (entity != null)
{
entity.Type = indicator.Type;
entity.SignalType = indicator.SignalType;
entity.MinimumHistory = indicator.MinimumHistory;
entity.Period = indicator.Period;
entity.FastPeriods = indicator.FastPeriods;
entity.SlowPeriods = indicator.SlowPeriods;
entity.SignalPeriods = indicator.SignalPeriods;
entity.Multiplier = indicator.Multiplier;
entity.SmoothPeriods = indicator.SmoothPeriods;
entity.StochPeriods = indicator.StochPeriods;
entity.CyclePeriods = indicator.CyclePeriods;
entity.UserName = indicator.User?.Name;
entity.Type = indicatorBase.Type;
entity.SignalType = indicatorBase.SignalType;
entity.MinimumHistory = indicatorBase.MinimumHistory;
entity.Period = indicatorBase.Period;
entity.FastPeriods = indicatorBase.FastPeriods;
entity.SlowPeriods = indicatorBase.SlowPeriods;
entity.SignalPeriods = indicatorBase.SignalPeriods;
entity.Multiplier = indicatorBase.Multiplier;
entity.SmoothPeriods = indicatorBase.SmoothPeriods;
entity.StochPeriods = indicatorBase.StochPeriods;
entity.CyclePeriods = indicatorBase.CyclePeriods;
entity.UserName = indicatorBase.User?.Name;
entity.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
}
}
@@ -242,15 +243,9 @@ public class PostgreSqlTradingRepository : ITradingRepository
#endregion
#region Position Methods
public Position GetPositionByIdentifier(string identifier)
{
return GetPositionByIdentifierAsync(identifier).Result;
}
public async Task<Position> GetPositionByIdentifierAsync(string identifier)
public async Task<Position> GetPositionByIdentifierAsync(Guid identifier)
{
var position = await _context.Positions
.AsNoTracking()
@@ -310,8 +305,8 @@ public class PostgreSqlTradingRepository : ITradingRepository
var existingPosition = await _context.Positions
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Identifier == position.Identifier &&
((position.User == null && p.UserName == null) ||
(position.User != null && p.UserName == position.User.Name)));
((position.User == null && p.UserName == null) ||
(position.User != null && p.UserName == position.User.Name)));
if (existingPosition != null)
{
@@ -320,7 +315,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
}
var entity = PostgreSqlMappers.Map(position);
// Handle related trades
if (position.Open != null)
{
@@ -370,11 +365,11 @@ public class PostgreSqlTradingRepository : ITradingRepository
entity.ProfitAndLoss = position.ProfitAndLoss?.Realized ?? 0;
entity.Status = position.Status;
entity.SignalIdentifier = position.SignalIdentifier;
entity.MoneyManagementJson = position.MoneyManagement != null
? JsonConvert.SerializeObject(position.MoneyManagement)
entity.MoneyManagementJson = position.MoneyManagement != null
? JsonConvert.SerializeObject(position.MoneyManagement)
: null;
entity.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
}
}
@@ -393,7 +388,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
var signals = await _context.Signals
.AsNoTracking()
.Where(s => (user == null && s.UserName == null) ||
(user != null && s.UserName == user.Name))
(user != null && s.UserName == user.Name))
.ToListAsync()
.ConfigureAwait(false);
@@ -410,8 +405,8 @@ public class PostgreSqlTradingRepository : ITradingRepository
var signal = await _context.Signals
.AsNoTracking()
.FirstOrDefaultAsync(s => s.Identifier == identifier &&
((user == null && s.UserName == null) ||
(user != null && s.UserName == user.Name)))
((user == null && s.UserName == null) ||
(user != null && s.UserName == user.Name)))
.ConfigureAwait(false);
return PostgreSqlMappers.Map(signal);
@@ -423,9 +418,9 @@ public class PostgreSqlTradingRepository : ITradingRepository
var existingSignal = _context.Signals
.AsNoTracking()
.FirstOrDefault(s => s.Identifier == signal.Identifier &&
s.Date == signal.Date &&
((s.UserName == null && signal.User == null) ||
(s.UserName != null && signal.User != null && s.UserName == signal.User.Name)));
s.Date == signal.Date &&
((s.UserName == null && signal.User == null) ||
(s.UserName != null && signal.User != null && s.UserName == signal.User.Name)));
if (existingSignal != null)
{
@@ -438,7 +433,25 @@ public class PostgreSqlTradingRepository : ITradingRepository
await _context.SaveChangesAsync();
}
public async Task<IndicatorBase> GetStrategyByNameUserAsync(string name, User user)
{
var indicator = await _context.Indicators
.AsNoTracking()
.FirstOrDefaultAsync(i => i.Name == name &&
((user == null && i.UserName == null) ||
(user != null && i.UserName == user.Name)));
return PostgreSqlMappers.Map(indicator);
}
public async Task<Scenario> GetScenarioByNameUserAsync(string scenarioName, User user)
{
var scenario = await _context.Scenarios
.AsNoTracking()
.FirstOrDefaultAsync(s => s.Name == scenarioName &&
((user == null && s.UserName == null) ||
(user != null && s.UserName == user.Name)));
return PostgreSqlMappers.Map(scenario);
}
#endregion
}
}