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

@@ -0,0 +1,99 @@
using Managing.Application.Abstractions.Grains;
using Managing.Application.Abstractions.Services;
using Managing.Core;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Indicators;
using Managing.Domain.Shared.Helpers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Orleans.Concurrency;
using static Managing.Common.Enums;
namespace Managing.Application.Scenarios;
/// <summary>
/// Orleans grain for scenario execution and signal generation.
/// This stateless grain handles candle management and signal generation for live trading.
/// </summary>
[StatelessWorker]
public class ScenarioRunnerGrain : Grain, IScenarioRunnerGrain
{
private readonly ILogger<ScenarioRunnerGrain> _logger;
private readonly IServiceScopeFactory _scopeFactory;
public ScenarioRunnerGrain(
ILogger<ScenarioRunnerGrain> logger,
IServiceScopeFactory scopeFactory)
{
_logger = logger;
_scopeFactory = scopeFactory;
}
private async Task<HashSet<Candle>> GetCandlesAsync(TradingBotConfig config, DateTime startDate)
{
try
{
var newCandles = await ServiceScopeHelpers.WithScopedService<IExchangeService, HashSet<Candle>>(
_scopeFactory, async exchangeService =>
{
return await exchangeService.GetCandlesInflux(
TradingExchanges.Evm,
config.Ticker,
startDate,
config.Timeframe,
500);
});
_logger.LogInformation($"Updated {newCandles.Count} candles for {config.Ticker}");
return newCandles;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to update candles for {Ticker}", config.Ticker);
throw;
}
}
public async Task<LightSignal> GetSignals(TradingBotConfig config, Dictionary<string, LightSignal> previousSignals,
DateTime startDate, Candle candle)
{
try
{
// return new LightSignal(config.Ticker, TradeDirection.Long, Confidence.High,
// candle, candle.Date, TradingExchanges.Evm, IndicatorType.Composite,
// SignalType.Signal, "Generated Signal");
var candlesHashSet = await GetCandlesAsync(config, startDate);
if (candlesHashSet.LastOrDefault()!.Date <= candle.Date)
{
return null; // No new candles, no need to generate a signal
}
var signal = TradingBox.GetSignal(
candlesHashSet,
config.Scenario,
previousSignals,
config.Scenario?.LoopbackPeriod ?? 1);
if (signal != null && signal.Date >= candle.Date)
{
_logger.LogInformation(
$"Generated signal for {config.Ticker}: {signal.Direction} with confidence {signal.Confidence}");
return new LightSignal(signal.Ticker, signal.Direction, Confidence.High,
candle, candle.Date, signal.Exchange, signal.IndicatorType,
signal.SignalType, signal.IndicatorName);
}
else
{
return null; // No signal generated
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to update signals for {Ticker}", config.Ticker);
throw;
}
}
}

View File

@@ -1,4 +1,5 @@
using Managing.Application.Abstractions;
using System.Data;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
@@ -25,7 +26,7 @@ namespace Managing.Application.Scenarios
foreach (var strategy in strategies)
{
scenario.AddIndicator(await _tradingService.GetStrategyByNameAsync(strategy));
scenario.AddIndicator(await _tradingService.GetIndicatorByNameAsync(strategy));
}
try
@@ -41,41 +42,14 @@ namespace Managing.Application.Scenarios
return scenario;
}
public async Task<Indicator> CreateStrategy(
IndicatorType type,
string name,
int? period = null,
int? fastPeriods = null,
int? slowPeriods = null,
int? signalPeriods = null,
double? multiplier = null,
int? stochPeriods = null,
int? smoothPeriods = null,
int? cyclePeriods = null)
{
var strategy = ScenarioHelpers.BuildIndicator(
type,
name,
period,
fastPeriods,
slowPeriods,
signalPeriods,
multiplier,
stochPeriods,
smoothPeriods,
cyclePeriods);
await _tradingService.InsertStrategyAsync(strategy);
return strategy;
}
public async Task<IEnumerable<Scenario>> GetScenariosAsync()
{
return await _tradingService.GetScenariosAsync();
}
public async Task<IEnumerable<Indicator>> GetIndicatorsAsync()
public async Task<IEnumerable<IndicatorBase>> GetIndicatorsAsync()
{
return await _tradingService.GetStrategiesAsync();
return await _tradingService.GetIndicatorsAsync();
}
public async Task<bool> DeleteScenarioAsync(string name)
@@ -100,7 +74,7 @@ namespace Managing.Application.Scenarios
scenario.Indicators.Clear();
foreach (var strategy in strategies)
{
scenario.AddIndicator(await _tradingService.GetStrategyByNameAsync(strategy));
scenario.AddIndicator(await _tradingService.GetIndicatorByNameAsync(strategy));
}
scenario.LoopbackPeriod = loopbackPeriod ?? 1;
@@ -120,7 +94,7 @@ namespace Managing.Application.Scenarios
{
try
{
var strategy = await _tradingService.GetStrategyByNameAsync(name);
var strategy = await _tradingService.GetIndicatorByNameAsync(name);
strategy.Type = indicatorType;
strategy.Period = period;
strategy.FastPeriods = fastPeriods;
@@ -130,7 +104,7 @@ namespace Managing.Application.Scenarios
strategy.StochPeriods = stochPeriods;
strategy.SmoothPeriods = smoothPeriods;
strategy.CyclePeriods = cyclePeriods;
await _tradingService.UpdateStrategyAsync(strategy);
await _tradingService.UpdateIndicatorAsync(strategy);
return true;
}
catch (Exception e)
@@ -156,7 +130,7 @@ namespace Managing.Application.Scenarios
foreach (var strategyName in strategies)
{
var strategy = await _tradingService.GetStrategyByNameAsync(strategyName);
var strategy = await _tradingService.GetIndicatorByNameAsync(strategyName);
if (strategy != null && strategy.User?.Name == user.Name)
{
scenario.AddIndicator(strategy);
@@ -167,7 +141,7 @@ namespace Managing.Application.Scenarios
return scenario;
}
public async Task<IEnumerable<Indicator>> GetIndicatorsByUserAsync(User user)
public async Task<IEnumerable<IndicatorBase>> GetIndicatorsByUserAsync(User user)
{
var indicators = await GetIndicatorsAsync();
return indicators.Where(s => s.User?.Name == user.Name);
@@ -175,10 +149,10 @@ namespace Managing.Application.Scenarios
public async Task<bool> DeleteIndicatorByUser(User user, string name)
{
var strategy = await _tradingService.GetStrategyByNameAsync(name);
var strategy = await _tradingService.GetIndicatorByNameAsync(name);
if (strategy != null && strategy.User?.Name == user.Name)
{
await _tradingService.DeleteStrategyAsync(strategy.Name);
await _tradingService.DeleteIndicatorAsync(strategy.Name);
return true;
}
@@ -229,23 +203,35 @@ namespace Managing.Application.Scenarios
return scenario != null && scenario.User?.Name == user.Name ? scenario : null;
}
public async Task<Indicator> CreateIndicatorForUser(User user, IndicatorType type, string name,
public async Task<IndicatorBase> CreateIndicatorForUser(User user, IndicatorType type, string name,
int? period = null,
int? fastPeriods = null, int? slowPeriods = null, int? signalPeriods = null,
double? multiplier = null, int? stochPeriods = null, int? smoothPeriods = null,
int? cyclePeriods = null)
{
// Create a new strategy using the existing implementation
var strategy = await CreateStrategy(type, name, period, fastPeriods, slowPeriods, signalPeriods,
multiplier, stochPeriods, smoothPeriods, cyclePeriods);
var existingIndicator = await _tradingService.GetIndicatorByNameUserAsync(name, user);
// Set the user
strategy.User = user;
// Update the strategy to save the user property
await _tradingService.UpdateStrategyAsync(strategy);
return strategy;
if (existingIndicator != null)
{
throw new DuplicateNameException("An indicator with this name already exists for the user.");
}
else
{
var indicator = new IndicatorBase(name, type)
{
Period = period,
FastPeriods = fastPeriods,
SlowPeriods = slowPeriods,
SignalPeriods = signalPeriods,
Multiplier = multiplier,
StochPeriods = stochPeriods,
SmoothPeriods = smoothPeriods,
CyclePeriods = cyclePeriods,
User = user
};
await _tradingService.InsertIndicatorAsync(indicator);
return indicator;
}
}
public async Task<bool> DeleteStrategiesByUser(User user)
@@ -255,7 +241,7 @@ namespace Managing.Application.Scenarios
var strategies = await GetIndicatorsByUserAsync(user);
foreach (var strategy in strategies)
{
await _tradingService.DeleteStrategyAsync(strategy.Name);
await _tradingService.DeleteIndicatorAsync(strategy.Name);
}
return true;
@@ -281,7 +267,7 @@ namespace Managing.Application.Scenarios
foreach (var strategyName in strategies)
{
var strategy = await _tradingService.GetStrategyByNameAsync(strategyName);
var strategy = await _tradingService.GetIndicatorByNameAsync(strategyName);
if (strategy != null && strategy.User?.Name == user.Name)
{
scenario.AddIndicator(strategy);
@@ -296,7 +282,7 @@ namespace Managing.Application.Scenarios
int? fastPeriods, int? slowPeriods, int? signalPeriods, double? multiplier,
int? stochPeriods, int? smoothPeriods, int? cyclePeriods)
{
var strategy = await _tradingService.GetStrategyByNameAsync(name);
var strategy = await _tradingService.GetIndicatorByNameAsync(name);
if (strategy == null || strategy.User?.Name != user.Name)
{
return false;
@@ -311,7 +297,7 @@ namespace Managing.Application.Scenarios
public async Task<Scenario> GetScenarioByNameAndUserAsync(string scenarioName, User user)
{
var scenario = await _tradingService.GetScenarioByNameAsync(scenarioName);
var scenario = await _tradingService.GetScenarioByNameUserAsync(scenarioName, user);
if (scenario == null)
{
throw new InvalidOperationException($"Scenario {scenarioName} not found for user {user.Name}");