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

@@ -1,6 +1,8 @@
using Managing.Core;
using Managing.Domain.Candles;
using Managing.Domain.Indicators;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using static Managing.Common.Enums;
@@ -49,28 +51,28 @@ public static class TradingBox
{
private static readonly IndicatorComboConfig _defaultConfig = new();
public static LightSignal GetSignal(HashSet<Candle> newCandles, HashSet<IIndicator> strategies,
HashSet<LightSignal> previousSignal, int? loopbackPeriod = 1)
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario scenario,
Dictionary<string, LightSignal> previousSignal, int? loopbackPeriod = 1)
{
return GetSignal(newCandles, strategies, previousSignal, _defaultConfig, loopbackPeriod);
return GetSignal(newCandles, scenario, previousSignal, _defaultConfig, loopbackPeriod);
}
public static LightSignal GetSignal(HashSet<Candle> newCandles, HashSet<IIndicator> strategies,
HashSet<LightSignal> previousSignal, IndicatorComboConfig config, int? loopbackPeriod = 1)
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario lightScenario,
Dictionary<string, LightSignal> previousSignal, IndicatorComboConfig config, int? loopbackPeriod = 1)
{
var signalOnCandles = new List<LightSignal>();
var limitedCandles = newCandles.ToList().TakeLast(600).ToList();
foreach (var strategy in strategies)
foreach (var indicator in lightScenario.Indicators)
{
strategy.UpdateCandles(limitedCandles.ToHashSet());
var signals = strategy.Run();
IIndicator indicatorInstance = indicator.ToInterface();
var signals = indicatorInstance.Run(newCandles);
if (signals == null || signals.Count == 0)
if (signals == null || signals.Count() == 0)
{
// For trend and context strategies, lack of signal might be meaningful
// Signal strategies are expected to be sparse, so we continue
if (strategy.SignalType == SignalType.Signal)
if (indicator.SignalType == SignalType.Signal)
{
continue;
}
@@ -96,10 +98,10 @@ public static class TradingBox
foreach (var signal in signals.Where(s => s.Date >= loopbackStartDate))
{
var hasExistingSignal = previousSignal.Any(s => s.Identifier == signal.Identifier);
var hasExistingSignal = previousSignal.ContainsKey(signal.Identifier);
if (!hasExistingSignal)
{
bool shouldAdd = previousSignal.Count == 0 || previousSignal.Last().Date < signal.Date;
bool shouldAdd = previousSignal.Count == 0 || previousSignal.Values.Last().Date < signal.Date;
if (shouldAdd)
{
signalOnCandles.Add(signal);
@@ -122,22 +124,22 @@ public static class TradingBox
}
var data = newCandles.First();
return ComputeSignals(strategies, latestSignalsPerIndicator, MiscExtensions.ParseEnum<Ticker>(data.Ticker),
return ComputeSignals(lightScenario, latestSignalsPerIndicator, MiscExtensions.ParseEnum<Ticker>(data.Ticker),
data.Timeframe, config);
}
public static LightSignal ComputeSignals(HashSet<IIndicator> strategies, HashSet<LightSignal> signalOnCandles,
public static LightSignal ComputeSignals(LightScenario scenario, HashSet<LightSignal> signalOnCandles,
Ticker ticker,
Timeframe timeframe)
{
return ComputeSignals(strategies, signalOnCandles, ticker, timeframe, _defaultConfig);
return ComputeSignals(scenario, signalOnCandles, ticker, timeframe, _defaultConfig);
}
public static LightSignal ComputeSignals(HashSet<IIndicator> strategies, HashSet<LightSignal> signalOnCandles,
public static LightSignal ComputeSignals(LightScenario scenario, HashSet<LightSignal> signalOnCandles,
Ticker ticker,
Timeframe timeframe, IndicatorComboConfig config)
{
if (strategies.Count == 1)
if (scenario.Indicators.Count == 1)
{
// Only one strategy, return the single signal
return signalOnCandles.Single();
@@ -146,7 +148,7 @@ public static class TradingBox
signalOnCandles = signalOnCandles.OrderBy(s => s.Date).ToHashSet();
// Check if all strategies produced signals - this is required for composite signals
var strategyNames = strategies.Select(s => s.Name).ToHashSet();
var strategyNames = scenario.Indicators.Select(s => s.Name).ToHashSet();
var signalIndicatorNames = signalOnCandles.Select(s => s.IndicatorName).ToHashSet();
if (!strategyNames.SetEquals(signalIndicatorNames))
@@ -161,7 +163,7 @@ public static class TradingBox
var contextSignals = signalOnCandles.Where(s => s.SignalType == SignalType.Context).ToList();
// Context validation - evaluates market conditions based on confidence levels
if (!ValidateContextStrategies(strategies, contextSignals, config))
if (!ValidateContextStrategies(scenario, contextSignals, config))
{
return null; // Context strategies are blocking the trade
}
@@ -233,10 +235,10 @@ public static class TradingBox
/// <summary>
/// Validates context strategies based on confidence levels indicating market condition quality
/// </summary>
private static bool ValidateContextStrategies(HashSet<IIndicator> allStrategies, List<LightSignal> contextSignals,
private static bool ValidateContextStrategies(LightScenario scenario, List<LightSignal> contextSignals,
IndicatorComboConfig config)
{
var contextStrategiesCount = allStrategies.Count(s => s.SignalType == SignalType.Context);
var contextStrategiesCount = scenario.Indicators.Count(s => s.SignalType == SignalType.Context);
if (contextStrategiesCount == 0)
{
@@ -453,11 +455,11 @@ public static class TradingBox
/// </summary>
/// <param name="positions">List of positions to analyze</param>
/// <returns>The total volume traded in decimal</returns>
public static decimal GetTotalVolumeTraded(List<Position> positions)
public static decimal GetTotalVolumeTraded(Dictionary<Guid, Position> positions)
{
decimal totalVolume = 0;
foreach (var position in positions)
foreach (var position in positions.Values)
{
// Add entry volume
totalVolume += position.Open.Quantity * position.Open.Price;
@@ -487,12 +489,12 @@ public static class TradingBox
/// </summary>
/// <param name="positions">List of positions to analyze</param>
/// <returns>The volume traded in the last 24 hours in decimal</returns>
public static decimal GetLast24HVolumeTraded(List<Position> positions)
public static decimal GetLast24HVolumeTraded(Dictionary<Guid, Position> positions)
{
decimal last24hVolume = 0;
DateTime cutoff = DateTime.UtcNow.AddHours(-24);
foreach (var position in positions)
foreach (var position in positions.Values)
{
// Check if any part of this position was traded in the last 24 hours
@@ -528,24 +530,20 @@ public static class TradingBox
/// </summary>
/// <param name="positions">List of positions to analyze</param>
/// <returns>A tuple containing (wins, losses)</returns>
public static (int Wins, int Losses) GetWinLossCount(List<Position> positions)
public static (int Wins, int Losses) GetWinLossCount(Dictionary<Guid, Position> positions)
{
int wins = 0;
int losses = 0;
foreach (var position in positions)
foreach (var position in positions.Values)
{
// Only count finished positions
if (position.IsFinished())
if (position.ProfitAndLoss != null && position.ProfitAndLoss.Realized > 0)
{
if (position.ProfitAndLoss != null && position.ProfitAndLoss.Realized > 0)
{
wins++;
}
else
{
losses++;
}
wins++;
}
else
{
losses++;
}
}
@@ -557,13 +555,13 @@ public static class TradingBox
/// </summary>
/// <param name="positions">List of positions to analyze</param>
/// <returns>The ROI for the last 24 hours as a percentage</returns>
public static decimal GetLast24HROI(List<Position> positions)
public static decimal GetLast24HROI(Dictionary<Guid, Position> positions)
{
decimal profitLast24h = 0;
decimal investmentLast24h = 0;
DateTime cutoff = DateTime.UtcNow.AddHours(-24);
foreach (var position in positions)
foreach (var position in positions.Values)
{
// Only count positions that were opened or closed within the last 24 hours
if (position.IsFinished() &&