feat(bot): add FinlyticBot autonomous paper trading microservice with Alpaca Markets API integration

This commit is contained in:
2026-08-17 16:32:47 +02:00
parent 3972507cb0
commit 5497cc5de7
21 changed files with 1924 additions and 14 deletions
@@ -0,0 +1,217 @@
// <auto-generated />
using System;
using FinlyticBot.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace FinlyticBot.Migrations
{
[DbContext(typeof(BotDbContext))]
[Migration("20260817142432_InitBotDatabase")]
partial class InitBotDatabase
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("FinlyticBot.Entities.BotAuditLogEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<decimal?>("AccountEquity")
.HasColumnType("decimal(18,4)");
b.Property<string>("Action")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<decimal?>("CalculatedSize")
.HasColumnType("decimal(18,4)");
b.Property<bool>("IsAccepted")
.HasColumnType("boolean");
b.Property<string>("Reason")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("Symbol")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("character varying(30)");
b.Property<DateTime>("Timestamp")
.HasColumnType("timestamp with time zone");
b.Property<string>("TradeId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.HasKey("Id");
b.HasIndex("Action");
b.HasIndex("Symbol");
b.HasIndex("Timestamp");
b.HasIndex("TradeId");
b.ToTable("BotAuditLogs");
});
modelBuilder.Entity("FinlyticBot.Entities.ExecutedPaperTradeEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<decimal?>("ActualFillPrice")
.HasColumnType("decimal(18,4)");
b.Property<Guid?>("AlpacaOrderId")
.HasColumnType("uuid");
b.Property<decimal>("CalculatedCrv")
.HasColumnType("decimal(18,4)");
b.Property<DateTime?>("ClosedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CompanyName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<DateTime?>("FilledAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Isin")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("character varying(30)");
b.Property<DateTime>("PlacedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal>("Quantity")
.HasColumnType("decimal(18,4)");
b.Property<decimal>("RealizedPnl")
.HasColumnType("decimal(18,4)");
b.Property<decimal?>("RealizedPnlPercent")
.HasColumnType("decimal(18,4)");
b.Property<string>("RejectReason")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<string>("Side")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<decimal>("SignalEntryPrice")
.HasColumnType("decimal(18,4)");
b.Property<decimal?>("SlippagePercent")
.HasColumnType("decimal(18,4)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("character varying(30)");
b.Property<decimal>("StopLossPrice")
.HasColumnType("decimal(18,4)");
b.Property<string>("Symbol")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("character varying(30)");
b.Property<decimal>("TakeProfitPrice1")
.HasColumnType("decimal(18,4)");
b.Property<decimal?>("TakeProfitPrice2")
.HasColumnType("decimal(18,4)");
b.Property<string>("TradeId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<double>("WinRate")
.HasColumnType("double precision");
b.HasKey("Id");
b.HasIndex("AlpacaOrderId");
b.HasIndex("Isin");
b.HasIndex("PlacedAt");
b.HasIndex("Status");
b.HasIndex("Symbol");
b.HasIndex("TradeId")
.IsUnique();
b.ToTable("ExecutedPaperTrades");
});
modelBuilder.Entity("FinlyticCore.Entities.Settings.SettingEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(150)
.HasColumnType("character varying(150)");
b.Property<DateTime>("LastUpdatedUtc")
.HasColumnType("timestamp with time zone");
b.Property<string>("ServiceIdentifier")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("ValueJson")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Key")
.IsUnique();
b.ToTable("DynamicSettings");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,153 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace FinlyticBot.Migrations
{
/// <inheritdoc />
public partial class InitBotDatabase : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BotAuditLogs",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TradeId = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
Symbol = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
Action = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
IsAccepted = table.Column<bool>(type: "boolean", nullable: false),
Reason = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
CalculatedSize = table.Column<decimal>(type: "numeric(18,4)", nullable: true),
AccountEquity = table.Column<decimal>(type: "numeric(18,4)", nullable: true),
Timestamp = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BotAuditLogs", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DynamicSettings",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Key = table.Column<string>(type: "character varying(150)", maxLength: 150, nullable: false),
ValueJson = table.Column<string>(type: "text", nullable: false),
ServiceIdentifier = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
LastUpdatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DynamicSettings", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ExecutedPaperTrades",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TradeId = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
Symbol = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
Isin = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
CompanyName = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
AlpacaOrderId = table.Column<Guid>(type: "uuid", nullable: true),
Side = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
Quantity = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
SignalEntryPrice = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
ActualFillPrice = table.Column<decimal>(type: "numeric(18,4)", nullable: true),
StopLossPrice = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
TakeProfitPrice1 = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
TakeProfitPrice2 = table.Column<decimal>(type: "numeric(18,4)", nullable: true),
CalculatedCrv = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
WinRate = table.Column<double>(type: "double precision", nullable: false),
SlippagePercent = table.Column<decimal>(type: "numeric(18,4)", nullable: true),
RealizedPnl = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
RealizedPnlPercent = table.Column<decimal>(type: "numeric(18,4)", nullable: true),
Status = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
RejectReason = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
PlacedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
FilledAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
ClosedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ExecutedPaperTrades", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_BotAuditLogs_Action",
table: "BotAuditLogs",
column: "Action");
migrationBuilder.CreateIndex(
name: "IX_BotAuditLogs_Symbol",
table: "BotAuditLogs",
column: "Symbol");
migrationBuilder.CreateIndex(
name: "IX_BotAuditLogs_Timestamp",
table: "BotAuditLogs",
column: "Timestamp");
migrationBuilder.CreateIndex(
name: "IX_BotAuditLogs_TradeId",
table: "BotAuditLogs",
column: "TradeId");
migrationBuilder.CreateIndex(
name: "IX_DynamicSettings_Key",
table: "DynamicSettings",
column: "Key",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ExecutedPaperTrades_AlpacaOrderId",
table: "ExecutedPaperTrades",
column: "AlpacaOrderId");
migrationBuilder.CreateIndex(
name: "IX_ExecutedPaperTrades_Isin",
table: "ExecutedPaperTrades",
column: "Isin");
migrationBuilder.CreateIndex(
name: "IX_ExecutedPaperTrades_PlacedAt",
table: "ExecutedPaperTrades",
column: "PlacedAt");
migrationBuilder.CreateIndex(
name: "IX_ExecutedPaperTrades_Status",
table: "ExecutedPaperTrades",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_ExecutedPaperTrades_Symbol",
table: "ExecutedPaperTrades",
column: "Symbol");
migrationBuilder.CreateIndex(
name: "IX_ExecutedPaperTrades_TradeId",
table: "ExecutedPaperTrades",
column: "TradeId",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "BotAuditLogs");
migrationBuilder.DropTable(
name: "DynamicSettings");
migrationBuilder.DropTable(
name: "ExecutedPaperTrades");
}
}
}
@@ -0,0 +1,214 @@
// <auto-generated />
using System;
using FinlyticBot.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace FinlyticBot.Migrations
{
[DbContext(typeof(BotDbContext))]
partial class BotDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("FinlyticBot.Entities.BotAuditLogEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<decimal?>("AccountEquity")
.HasColumnType("decimal(18,4)");
b.Property<string>("Action")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<decimal?>("CalculatedSize")
.HasColumnType("decimal(18,4)");
b.Property<bool>("IsAccepted")
.HasColumnType("boolean");
b.Property<string>("Reason")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("Symbol")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("character varying(30)");
b.Property<DateTime>("Timestamp")
.HasColumnType("timestamp with time zone");
b.Property<string>("TradeId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.HasKey("Id");
b.HasIndex("Action");
b.HasIndex("Symbol");
b.HasIndex("Timestamp");
b.HasIndex("TradeId");
b.ToTable("BotAuditLogs");
});
modelBuilder.Entity("FinlyticBot.Entities.ExecutedPaperTradeEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<decimal?>("ActualFillPrice")
.HasColumnType("decimal(18,4)");
b.Property<Guid?>("AlpacaOrderId")
.HasColumnType("uuid");
b.Property<decimal>("CalculatedCrv")
.HasColumnType("decimal(18,4)");
b.Property<DateTime?>("ClosedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CompanyName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<DateTime?>("FilledAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Isin")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("character varying(30)");
b.Property<DateTime>("PlacedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal>("Quantity")
.HasColumnType("decimal(18,4)");
b.Property<decimal>("RealizedPnl")
.HasColumnType("decimal(18,4)");
b.Property<decimal?>("RealizedPnlPercent")
.HasColumnType("decimal(18,4)");
b.Property<string>("RejectReason")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<string>("Side")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<decimal>("SignalEntryPrice")
.HasColumnType("decimal(18,4)");
b.Property<decimal?>("SlippagePercent")
.HasColumnType("decimal(18,4)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("character varying(30)");
b.Property<decimal>("StopLossPrice")
.HasColumnType("decimal(18,4)");
b.Property<string>("Symbol")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("character varying(30)");
b.Property<decimal>("TakeProfitPrice1")
.HasColumnType("decimal(18,4)");
b.Property<decimal?>("TakeProfitPrice2")
.HasColumnType("decimal(18,4)");
b.Property<string>("TradeId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<double>("WinRate")
.HasColumnType("double precision");
b.HasKey("Id");
b.HasIndex("AlpacaOrderId");
b.HasIndex("Isin");
b.HasIndex("PlacedAt");
b.HasIndex("Status");
b.HasIndex("Symbol");
b.HasIndex("TradeId")
.IsUnique();
b.ToTable("ExecutedPaperTrades");
});
modelBuilder.Entity("FinlyticCore.Entities.Settings.SettingEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(150)
.HasColumnType("character varying(150)");
b.Property<DateTime>("LastUpdatedUtc")
.HasColumnType("timestamp with time zone");
b.Property<string>("ServiceIdentifier")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("ValueJson")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Key")
.IsUnique();
b.ToTable("DynamicSettings");
});
#pragma warning restore 612, 618
}
}
}