feat(analyzer): update MQTT handler, active trade monitoring, and dynamic settings
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using FinlyticAnalyzer.Entities;
|
||||
using FinlyticCore.Entities.Settings;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FinlyticAnalyzer.Database;
|
||||
@@ -7,6 +8,7 @@ public class AnalyzerDbContext : DbContext
|
||||
{
|
||||
public AnalyzerDbContext(DbContextOptions<AnalyzerDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<SettingEntity> DynamicSettings => Set<SettingEntity>();
|
||||
public DbSet<AnalysisEntity> Analyses => Set<AnalysisEntity>();
|
||||
public DbSet<AnalyzerSettingsEntity> Settings => Set<AnalyzerSettingsEntity>();
|
||||
public DbSet<TradeProposalEntity> TradeProposals => Set<TradeProposalEntity>();
|
||||
@@ -15,6 +17,12 @@ public class AnalyzerDbContext : DbContext
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<SettingEntity>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.HasIndex(e => e.Key);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<AnalysisEntity>(entity =>
|
||||
{
|
||||
entity.HasIndex(e => e.AnalysisId).IsUnique();
|
||||
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FinlyticAnalyzer.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 FinlyticAnalyzer.Migrations
|
||||
{
|
||||
[DbContext(typeof(AnalyzerDbContext))]
|
||||
[Migration("20260813202556_CheckPendingAnalyzer")]
|
||||
partial class CheckPendingAnalyzer
|
||||
{
|
||||
/// <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("FinlyticAnalyzer.Entities.AnalysisEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AiOutputJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<string>("AnalysisId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("EventId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<double>("ImpactScore")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<bool>("IsTradeProposed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Isin")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("N8nDecision")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<double>("N8nEvalScore")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<string>("N8nResponseJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<string>("RawDataJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<string>("Sector")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<int>("VixRegime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<decimal>("VixValue")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<double>("WinRate")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AnalysisId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("EventId");
|
||||
|
||||
b.HasIndex("Isin");
|
||||
|
||||
b.HasIndex("Sector");
|
||||
|
||||
b.ToTable("analyses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FinlyticAnalyzer.Entities.AnalyzerSettingsEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("EnableLogAnalyzerAuto")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("EnableLogAnalyzerManual")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("EnableLogDatabaseOps")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("EnableLogMqttGeneral")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("EnableLogMqttHealthPing")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<double>("MinSignalScore")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<string>("ScanCronSchedule")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FinlyticAnalyzer.Entities.TradeProposalEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AnalysisId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<double>("ConfidenceScore")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<decimal>("EntryPrice")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<decimal?>("EntryZoneMax")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<decimal?>("EntryZoneMin")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("EventId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("FundamentalRationale")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("InstrumentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("Isin")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<decimal?>("MaxLeverage")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(150)
|
||||
.HasColumnType("character varying(150)");
|
||||
|
||||
b.Property<string>("ProposedAction")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.Property<string>("ReasonSummary")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal?>("RiskRewardRatio")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("RiskTolerance")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("RiskWarning")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Sector")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<decimal>("StopLoss")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<decimal>("TakeProfit")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("TakeProfitTargets")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("TechnicalRationale")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Timeframe")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("VixRegime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<decimal>("VixValue")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<double>("WinRate")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ExpiresAt");
|
||||
|
||||
b.HasIndex("Isin");
|
||||
|
||||
b.ToTable("trade_proposals");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FinlyticAnalyzer.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CheckPendingAnalyzer : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_TradeProposals",
|
||||
table: "TradeProposals");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "TradeProposals",
|
||||
newName: "trade_proposals");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_TradeProposals_Isin",
|
||||
table: "trade_proposals",
|
||||
newName: "IX_trade_proposals_Isin");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_TradeProposals_ExpiresAt",
|
||||
table: "trade_proposals",
|
||||
newName: "IX_trade_proposals_ExpiresAt");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "ProposedAction",
|
||||
table: "trade_proposals",
|
||||
type: "character varying(20)",
|
||||
maxLength: 20,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
table: "trade_proposals",
|
||||
type: "character varying(150)",
|
||||
maxLength: 150,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Isin",
|
||||
table: "trade_proposals",
|
||||
type: "character varying(30)",
|
||||
maxLength: 30,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "AnalysisId",
|
||||
table: "trade_proposals",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "EntryPrice",
|
||||
table: "trade_proposals",
|
||||
type: "numeric(18,4)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "EntryZoneMax",
|
||||
table: "trade_proposals",
|
||||
type: "numeric(18,4)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "EntryZoneMin",
|
||||
table: "trade_proposals",
|
||||
type: "numeric(18,4)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "EventId",
|
||||
table: "trade_proposals",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "FundamentalRationale",
|
||||
table: "trade_proposals",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "InstrumentType",
|
||||
table: "trade_proposals",
|
||||
type: "character varying(30)",
|
||||
maxLength: 30,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "MaxLeverage",
|
||||
table: "trade_proposals",
|
||||
type: "numeric(18,4)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "RiskRewardRatio",
|
||||
table: "trade_proposals",
|
||||
type: "numeric(18,4)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "RiskTolerance",
|
||||
table: "trade_proposals",
|
||||
type: "character varying(30)",
|
||||
maxLength: 30,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "RiskWarning",
|
||||
table: "trade_proposals",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Sector",
|
||||
table: "trade_proposals",
|
||||
type: "character varying(50)",
|
||||
maxLength: 50,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "StopLoss",
|
||||
table: "trade_proposals",
|
||||
type: "numeric(18,4)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Symbol",
|
||||
table: "trade_proposals",
|
||||
type: "character varying(30)",
|
||||
maxLength: 30,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "TakeProfit",
|
||||
table: "trade_proposals",
|
||||
type: "numeric(18,4)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "TakeProfitTargets",
|
||||
table: "trade_proposals",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "TechnicalRationale",
|
||||
table: "trade_proposals",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Timeframe",
|
||||
table: "trade_proposals",
|
||||
type: "character varying(20)",
|
||||
maxLength: 20,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "VixRegime",
|
||||
table: "trade_proposals",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "VixValue",
|
||||
table: "trade_proposals",
|
||||
type: "numeric(18,4)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<double>(
|
||||
name: "WinRate",
|
||||
table: "trade_proposals",
|
||||
type: "double precision",
|
||||
nullable: false,
|
||||
defaultValue: 0.0);
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_trade_proposals",
|
||||
table: "trade_proposals",
|
||||
column: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_trade_proposals",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AnalysisId",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "EntryPrice",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "EntryZoneMax",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "EntryZoneMin",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "EventId",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FundamentalRationale",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "InstrumentType",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxLeverage",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RiskRewardRatio",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RiskTolerance",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RiskWarning",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Sector",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "StopLoss",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Symbol",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TakeProfit",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TakeProfitTargets",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TechnicalRationale",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Timeframe",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "VixRegime",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "VixValue",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WinRate",
|
||||
table: "trade_proposals");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "trade_proposals",
|
||||
newName: "TradeProposals");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_trade_proposals_Isin",
|
||||
table: "TradeProposals",
|
||||
newName: "IX_TradeProposals_Isin");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_trade_proposals_ExpiresAt",
|
||||
table: "TradeProposals",
|
||||
newName: "IX_TradeProposals_ExpiresAt");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "ProposedAction",
|
||||
table: "TradeProposals",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(20)",
|
||||
oldMaxLength: 20);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
table: "TradeProposals",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(150)",
|
||||
oldMaxLength: 150);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Isin",
|
||||
table: "TradeProposals",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(30)",
|
||||
oldMaxLength: 30);
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_TradeProposals",
|
||||
table: "TradeProposals",
|
||||
column: "Id");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,41 +149,124 @@ namespace FinlyticAnalyzer.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AnalysisId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<double>("ConfidenceScore")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<decimal>("EntryPrice")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<decimal?>("EntryZoneMax")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<decimal?>("EntryZoneMin")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("EventId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Isin")
|
||||
b.Property<string>("FundamentalRationale")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("InstrumentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("Isin")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<decimal?>("MaxLeverage")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
.HasMaxLength(150)
|
||||
.HasColumnType("character varying(150)");
|
||||
|
||||
b.Property<string>("ProposedAction")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.Property<string>("ReasonSummary")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal?>("RiskRewardRatio")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("RiskTolerance")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("RiskWarning")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Sector")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<decimal>("StopLoss")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<decimal>("TakeProfit")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("TakeProfitTargets")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("TechnicalRationale")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Timeframe")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("VixRegime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<decimal>("VixValue")
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<double>("WinRate")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ExpiresAt");
|
||||
|
||||
b.HasIndex("Isin");
|
||||
|
||||
b.ToTable("TradeProposals");
|
||||
b.ToTable("trade_proposals");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ builder.Services.AddSingleton<IVixTrackerService, VixTrackerService>();
|
||||
builder.Services.AddSingleton<IThreeLayerFilterEngine, ThreeLayerFilterEngine>();
|
||||
builder.Services.AddSingleton<IWinRateCalculator, WinRateCalculator>();
|
||||
builder.Services.AddScoped<ISettingsDbService, SettingsDbService>();
|
||||
builder.Services.AddScoped<YahooFinanceClient>();
|
||||
builder.Services.AddSingleton<YahooFinanceClient>();
|
||||
|
||||
// Unified MQTT Client (Handles both Events and RPC)
|
||||
builder.Services.AddSingleton<AnalyzerMqttClient>();
|
||||
|
||||
@@ -211,8 +211,8 @@ public class ActiveTradeMonitorWorker : BackgroundService
|
||||
},
|
||||
MarketContext = new MarketContextInfo
|
||||
{
|
||||
Vix = (double)vixService.CurrentVix,
|
||||
MarketRegime = vixService.CurrentRegime.ToString()
|
||||
Vix = vixService.GetCurrentVix(),
|
||||
MarketRegime = vixService.GetCurrentRegime().ToString()
|
||||
},
|
||||
UserPreferences = new UserPreferencesInfo
|
||||
{
|
||||
|
||||
@@ -14,7 +14,11 @@ public class N8nEvaluationService : IN8nEvaluationService
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_webhookUrl = configuration["N8N:WebhookUrl"] ?? configuration["N8N__WebhookUrl"] ?? "https://n8n.kleidukos.me/webhook/gemini/analysis/auto";
|
||||
_webhookUrl = configuration["N8N:WebhookUrl"] ?? configuration["N8N__WebhookUrl"] ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(_webhookUrl))
|
||||
{
|
||||
_logger.LogWarning("[{Channel}] N8N:WebhookUrl configuration is missing or empty.", "AnalyzerChannel");
|
||||
}
|
||||
|
||||
// Timeout auf 45 Sekunden erhöht für komplexere LLM/Gemini Chains in n8n
|
||||
_httpClient.Timeout = TimeSpan.FromSeconds(45);
|
||||
@@ -25,6 +29,12 @@ public class N8nEvaluationService : IN8nEvaluationService
|
||||
/// </summary>
|
||||
public async Task<N8nAnalysisResponseDto?> EvaluateAssetAsync(N8nAnalysisRequestDto request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_webhookUrl))
|
||||
{
|
||||
_logger.LogError("[{Channel}] Cannot execute AI evaluation for {Symbol}: N8N:WebhookUrl is not configured.", "AnalyzerChannel", request.TargetAsset.Symbol);
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("[{Channel}] Sending n8n AI Evaluation request {RequestId} for Asset {Symbol} (ISIN: {Isin}) to {Url}...",
|
||||
|
||||
@@ -180,8 +180,8 @@ public class AnalyzerMqttClient : ManagedMqttClient, IHostedService
|
||||
|
||||
if (closedDto != null && !string.IsNullOrWhiteSpace(closedDto.TradeId))
|
||||
{
|
||||
bool isWin = closedDto.Status.Contains("Profit", StringComparison.OrdinalIgnoreCase) ||
|
||||
closedDto.Status.Contains("Win", StringComparison.OrdinalIgnoreCase);
|
||||
bool isWin = closedDto.Status?.Contains("Profit", StringComparison.OrdinalIgnoreCase) == true ||
|
||||
closedDto.Status?.Contains("Win", StringComparison.OrdinalIgnoreCase) == true;
|
||||
|
||||
var feedback = new TradeFeedbackRecord
|
||||
{
|
||||
@@ -252,8 +252,8 @@ public class AnalyzerMqttClient : ManagedMqttClient, IHostedService
|
||||
TriggerType = "Manual",
|
||||
TargetAsset = new TargetAssetInfo
|
||||
{
|
||||
Symbol = manualReq.FundamentalsData?.Ticker ?? manualReq.Symbol.ToUpperInvariant(),
|
||||
Name = manualReq.FundamentalsData?.CompanyName ?? manualReq.Isin.ToUpperInvariant(),
|
||||
Symbol = manualReq.FundamentalsData?.Fundamentals?.Ticker?.Ticker ?? manualReq.FundamentalsData?.Asset?.PrimaryTicker?.Ticker ?? manualReq.Symbol.ToUpperInvariant(),
|
||||
Name = !string.IsNullOrWhiteSpace(manualReq.FundamentalsData?.Asset?.Name) ? manualReq.FundamentalsData.Asset.Name : manualReq.Isin.ToUpperInvariant(),
|
||||
Isin = manualReq.Isin.ToUpperInvariant(),
|
||||
Sector = manualReq.Sector
|
||||
},
|
||||
@@ -308,18 +308,18 @@ public class AnalyzerMqttClient : ManagedMqttClient, IHostedService
|
||||
},
|
||||
FundamentalContext = new FundamentalContextInfo
|
||||
{
|
||||
PeRatio = (double?)manualReq.FundamentalsData?.PeRatioTrailing,
|
||||
ForwardPeRatio = (double?)manualReq.FundamentalsData?.PeRatioForward,
|
||||
PegRatio = (double?)manualReq.FundamentalsData?.PegRatio,
|
||||
MarketCap = (double?)manualReq.FundamentalsData?.MarketCapitalization,
|
||||
DebtToEquity = (double?)manualReq.FundamentalsData?.DebtToEquity,
|
||||
GrossMargin = (double?)manualReq.FundamentalsData?.GrossMargin,
|
||||
NetProfitMargin = (double?)manualReq.FundamentalsData?.NetProfitMargin,
|
||||
ReturnOnEquity = (double?)manualReq.FundamentalsData?.ReturnOnEquity,
|
||||
DividendYield = (double?)manualReq.FundamentalsData?.DividendYield,
|
||||
ShortPercentOfFloat = (double?)manualReq.FundamentalsData?.ShortPercentOfFloat,
|
||||
AnalystTargetMedian = (double?)manualReq.FundamentalsData?.PriceTargetMedian,
|
||||
EvToEbitda = (double?)manualReq.FundamentalsData?.EvToEbitda
|
||||
PeRatio = (double?)manualReq.FundamentalsData?.Fundamentals?.TrailingPe,
|
||||
ForwardPeRatio = (double?)manualReq.FundamentalsData?.Fundamentals?.ForwardPe,
|
||||
PegRatio = (double?)manualReq.FundamentalsData?.Fundamentals?.PegRatio,
|
||||
MarketCap = (double?)manualReq.FundamentalsData?.Fundamentals?.MarketCap,
|
||||
DebtToEquity = (double?)manualReq.FundamentalsData?.Fundamentals?.DebtToEquity,
|
||||
GrossMargin = (double?)manualReq.FundamentalsData?.Fundamentals?.GrossProfit,
|
||||
NetProfitMargin = (double?)manualReq.FundamentalsData?.Fundamentals?.NetIncome,
|
||||
ReturnOnEquity = (double?)manualReq.FundamentalsData?.Fundamentals?.ReturnOnEquity,
|
||||
DividendYield = (double?)manualReq.FundamentalsData?.Fundamentals?.ForwardDividendYield,
|
||||
ShortPercentOfFloat = null,
|
||||
AnalystTargetMedian = null,
|
||||
EvToEbitda = (double?)manualReq.FundamentalsData?.Fundamentals?.EvToEbitda
|
||||
}
|
||||
};
|
||||
|
||||
@@ -346,7 +346,7 @@ public class AnalyzerMqttClient : ManagedMqttClient, IHostedService
|
||||
Sector = manualReq.Sector,
|
||||
Symbol = manualReq.Symbol.ToUpperInvariant(),
|
||||
Isin = manualReq.Isin.ToUpperInvariant(),
|
||||
CompanyName = manualReq.FundamentalsData?.CompanyName ?? manualReq.Symbol,
|
||||
CompanyName = !string.IsNullOrWhiteSpace(manualReq.FundamentalsData?.Asset?.Name) ? manualReq.FundamentalsData.Asset.Name : manualReq.Symbol,
|
||||
EntryPrice = manualReq.CurrentPrice,
|
||||
SignalType = string.Equals(n8nResponse.SuggestedDirection, "Short", StringComparison.OrdinalIgnoreCase) ? "SELL" : "BUY",
|
||||
Status = shouldProceed ? "Proposed" : "Rejected",
|
||||
@@ -427,8 +427,7 @@ public class AnalyzerMqttClient : ManagedMqttClient, IHostedService
|
||||
Status = "ERROR",
|
||||
Message = $"Analysis failed: {ex.Message}"
|
||||
};
|
||||
await PublishAsync($"services/response/analyzer_TriggerManual/{correlationId}",
|
||||
JsonSerializer.Serialize(errorResponse, FinlyticJsonSerializerContext.Default.ManualAnalysisResponseDto));
|
||||
await PublishAsync($"services/response/analyzer_TriggerManual/{correlationId}", errorResponse);
|
||||
}
|
||||
catch (Exception pubEx)
|
||||
{
|
||||
@@ -574,33 +573,38 @@ public class AnalyzerMqttClient : ManagedMqttClient, IHostedService
|
||||
|
||||
if (fundResp != null)
|
||||
{
|
||||
resolvedSymbol = !string.IsNullOrWhiteSpace(fundResp.Ticker) ? fundResp.Ticker : resolvedSymbol;
|
||||
resolvedName = !string.IsNullOrWhiteSpace(fundResp.CompanyName) ? fundResp.CompanyName : resolvedName;
|
||||
string? fundTicker = fundResp.Fundamentals?.Ticker?.Ticker ?? fundResp.Asset?.PrimaryTicker?.Ticker;
|
||||
resolvedSymbol = !string.IsNullOrWhiteSpace(fundTicker) ? fundTicker : resolvedSymbol;
|
||||
resolvedName = !string.IsNullOrWhiteSpace(fundResp.Asset?.Name) ? fundResp.Asset.Name : resolvedName;
|
||||
|
||||
fundInfo = new FundamentalContextInfo
|
||||
{
|
||||
PeRatio = (double?)fundResp.PeRatioTrailing,
|
||||
ForwardPeRatio = (double?)fundResp.PeRatioForward,
|
||||
PegRatio = (double?)fundResp.PegRatio,
|
||||
MarketCap = (double?)fundResp.MarketCapitalization,
|
||||
DebtToEquity = (double?)fundResp.DebtToEquity,
|
||||
GrossMargin = (double?)fundResp.GrossMargin,
|
||||
NetProfitMargin = (double?)fundResp.NetProfitMargin,
|
||||
ReturnOnEquity = (double?)fundResp.ReturnOnEquity,
|
||||
DividendYield = (double?)fundResp.DividendYield,
|
||||
ShortPercentOfFloat = (double?)fundResp.ShortPercentOfFloat,
|
||||
AnalystTargetMedian = (double?)fundResp.PriceTargetMedian,
|
||||
EvToEbitda = (double?)fundResp.EvToEbitda
|
||||
PeRatio = (double?)fundResp.Fundamentals?.TrailingPe,
|
||||
ForwardPeRatio = (double?)fundResp.Fundamentals?.ForwardPe,
|
||||
PegRatio = (double?)fundResp.Fundamentals?.PegRatio,
|
||||
MarketCap = (double?)fundResp.Fundamentals?.MarketCap,
|
||||
DebtToEquity = (double?)fundResp.Fundamentals?.DebtToEquity,
|
||||
GrossMargin = (double?)fundResp.Fundamentals?.GrossProfit,
|
||||
NetProfitMargin = (double?)fundResp.Fundamentals?.NetIncome,
|
||||
ReturnOnEquity = (double?)fundResp.Fundamentals?.ReturnOnEquity,
|
||||
DividendYield = (double?)fundResp.Fundamentals?.ForwardDividendYield,
|
||||
ShortPercentOfFloat = null,
|
||||
AnalystTargetMedian = null,
|
||||
EvToEbitda = (double?)fundResp.Fundamentals?.EvToEbitda
|
||||
};
|
||||
}
|
||||
|
||||
if (sentResp != null)
|
||||
{
|
||||
double compound = sentResp.CurrentSummary?.CompoundScore ?? 0.0;
|
||||
// FinBERT compound score is in range [-1.0, +1.0]. Normalize to [0.0, 1.0] for AI prompt context
|
||||
double normalizedScore = Math.Clamp((compound + 1.0) / 2.0, 0.0, 1.0);
|
||||
|
||||
sentInfo = new SentimentContextInfo
|
||||
{
|
||||
AssetSentimentScore = sentResp.CurrentSummary?.CompoundScore ?? 0.0,
|
||||
SectorSentimentScore = 0.5,
|
||||
NewsSentimentSummary = sentResp.CurrentSummary?.SentimentLabel ?? "Neutral"
|
||||
AssetSentimentScore = Math.Round(normalizedScore, 2),
|
||||
SectorSentimentScore = Math.Round(normalizedScore, 2),
|
||||
NewsSentimentSummary = string.IsNullOrWhiteSpace(sentResp.CurrentSummary?.SentimentLabel) ? "Neutral" : sentResp.CurrentSummary.SentimentLabel
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -684,7 +688,7 @@ public class AnalyzerMqttClient : ManagedMqttClient, IHostedService
|
||||
var supportLevels = new List<double>();
|
||||
var resistanceLevels = new List<double>();
|
||||
|
||||
double currentPrice = (double)(livePriceResp?.CurrentPrice > 0 ? livePriceResp.CurrentPrice : (fundResp?.CurrentPrice > 0 ? fundResp.CurrentPrice : 0.0m));
|
||||
double currentPrice = (double)(livePriceResp?.CurrentPrice > 0 ? livePriceResp.CurrentPrice : 0.0m);
|
||||
if (currentPrice > 0)
|
||||
{
|
||||
supportLevels.Add(Math.Round(currentPrice * 0.98, 2));
|
||||
|
||||
Reference in New Issue
Block a user