Add synthApi (#27)
* Add synthApi * Put confidence for Synth proba * Update the code * Update readme * Fix bootstraping * fix github build * Update the endpoints for scenario * Add scenario and update backtest modal * Update bot modal * Update interfaces for synth * add synth to backtest * Add Kelly criterion and better signal * Update signal confidence * update doc * save leaderboard and prediction * Update nswag to generate ApiClient in the correct path * Unify the trading modal * Save miner and prediction * Update messaging and block new signal until position not close when flipping off * Rename strategies to indicators * Update doc * Update chart + add signal name * Fix signal direction * Update docker webui * remove crypto npm * Clean
This commit is contained in:
@@ -7,7 +7,6 @@ using Managing.Core.FixedSizedQueue;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Strategies;
|
||||
@@ -41,8 +40,6 @@ public class TradingBot : Bot, ITradingBot
|
||||
public DateTime PreloadSince { get; set; }
|
||||
public int PreloadedCandlesCount { get; set; }
|
||||
public decimal Fee { get; set; }
|
||||
public Scenario Scenario { get; set; }
|
||||
|
||||
|
||||
public TradingBot(
|
||||
IExchangeService exchangeService,
|
||||
@@ -132,6 +129,9 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
public void LoadScenario(string scenarioName)
|
||||
{
|
||||
if (Config.Scenario != null)
|
||||
return;
|
||||
|
||||
var scenario = TradingService.GetScenarioByName(scenarioName);
|
||||
if (scenario == null)
|
||||
{
|
||||
@@ -140,7 +140,6 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
else
|
||||
{
|
||||
Scenario = scenario;
|
||||
LoadIndicators(ScenarioHelpers.GetIndicatorsFromScenario(scenario));
|
||||
}
|
||||
}
|
||||
@@ -154,11 +153,15 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
else
|
||||
{
|
||||
Scenario = scenario;
|
||||
LoadIndicators(ScenarioHelpers.GetIndicatorsFromScenario(scenario));
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadIndicators(Scenario scenario)
|
||||
{
|
||||
LoadIndicators(ScenarioHelpers.GetIndicatorsFromScenario(scenario));
|
||||
}
|
||||
|
||||
public void LoadIndicators(IEnumerable<IIndicator> indicators)
|
||||
{
|
||||
foreach (var strategy in indicators)
|
||||
@@ -209,7 +212,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
|
||||
UpdateWalletBalances();
|
||||
if (OptimizedCandles.Count % 100 == 0) // Log every 10th execution
|
||||
if (!Config.IsForBacktest) // Log every 10th execution
|
||||
{
|
||||
Logger.LogInformation($"Candle date : {OptimizedCandles.Last().Date:u}");
|
||||
Logger.LogInformation($"Signals : {Signals.Count}");
|
||||
@@ -223,7 +226,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
foreach (var strategy in Indicators)
|
||||
{
|
||||
IndicatorsValues[strategy.Type] = ((Indicator)strategy).GetStrategyValues();
|
||||
IndicatorsValues[strategy.Type] = ((Indicator)strategy).GetIndicatorValues();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +263,10 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
private async Task UpdateSignals(FixedSizeQueue<Candle> candles)
|
||||
{
|
||||
var signal = TradingBox.GetSignal(candles.ToHashSet(), Indicators, Signals, Scenario.LoopbackPeriod);
|
||||
// If position open and not flipped, do not update signals
|
||||
if (!Config.FlipPosition && Positions.Any(p => !p.IsFinished())) return;
|
||||
|
||||
var signal = TradingBox.GetSignal(candles.ToHashSet(), Indicators, Signals, Config.Scenario.LoopbackPeriod);
|
||||
if (signal == null) return;
|
||||
|
||||
signal.User = Account.User;
|
||||
@@ -272,11 +278,39 @@ public class TradingBot : Bot, ITradingBot
|
||||
if (Config.IsForWatchingOnly || (ExecutionCount < 1 && !Config.IsForBacktest))
|
||||
signal.Status = SignalStatus.Expired;
|
||||
|
||||
Signals.Add(signal);
|
||||
|
||||
var signalText = $"{Config.ScenarioName} trigger a signal. Signal told you " +
|
||||
$"to {signal.Direction} {Config.Ticker} on {Config.Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}";
|
||||
|
||||
|
||||
// Apply Synth-based signal filtering if enabled
|
||||
if (Config.UseSynthApi)
|
||||
{
|
||||
var currentPrice = Config.IsForBacktest
|
||||
? OptimizedCandles.Last().Close
|
||||
: ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
|
||||
|
||||
var signalValidationResult = TradingService.ValidateSynthSignalAsync(signal, currentPrice, Config,
|
||||
Config.IsForBacktest).GetAwaiter().GetResult();
|
||||
|
||||
if (signalValidationResult.Confidence == Confidence.None ||
|
||||
signalValidationResult.Confidence == Confidence.Low ||
|
||||
signalValidationResult.IsBlocked)
|
||||
{
|
||||
signal.Status = SignalStatus.Expired;
|
||||
await LogInformation(
|
||||
$"🚫 **Synth Signal Filter** - Signal {signal.Identifier} blocked by Synth risk assessment. Context : {signalValidationResult.ValidationContext}");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
signal.SetConfidence(signalValidationResult.Confidence);
|
||||
signalText +=
|
||||
$" and Synth risk assessment passed. Context : {signalValidationResult.ValidationContext}";
|
||||
}
|
||||
}
|
||||
|
||||
Signals.Add(signal);
|
||||
|
||||
Logger.LogInformation(signalText);
|
||||
|
||||
if (Config.IsForWatchingOnly && !Config.IsForBacktest && ExecutionCount > 0)
|
||||
@@ -326,7 +360,8 @@ public class TradingBot : Bot, ITradingBot
|
||||
date: position.Open.Date,
|
||||
exchange: Account.Exchange,
|
||||
indicatorType: IndicatorType.Stc, // Use a valid strategy type for recreated signals
|
||||
signalType: SignalType.Signal
|
||||
signalType: SignalType.Signal,
|
||||
indicatorName: "RecreatedSignal"
|
||||
);
|
||||
|
||||
// Since Signal identifier is auto-generated, we need to update our position
|
||||
@@ -414,8 +449,6 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInformation($"📊 **Position Update**\nUpdating position: `{positionForSignal.SignalIdentifier}`");
|
||||
|
||||
var position = Config.IsForBacktest
|
||||
? positionForSignal
|
||||
: TradingService.GetPositionByIdentifier(positionForSignal.Identifier);
|
||||
@@ -624,6 +657,38 @@ public class TradingBot : Bot, ITradingBot
|
||||
await OpenPosition(signal);
|
||||
}
|
||||
}
|
||||
|
||||
// Synth-based position monitoring for liquidation risk
|
||||
if (Config.UseSynthApi && !Config.IsForBacktest &&
|
||||
positionForSignal.Status == PositionStatus.Filled)
|
||||
{
|
||||
var currentPrice = ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
|
||||
var riskResult = await TradingService.MonitorSynthPositionRiskAsync(
|
||||
Config.Ticker,
|
||||
positionForSignal.OriginDirection,
|
||||
currentPrice,
|
||||
positionForSignal.StopLoss.Price,
|
||||
positionForSignal.Identifier,
|
||||
Config);
|
||||
|
||||
if (riskResult != null && riskResult.ShouldWarn && !string.IsNullOrEmpty(riskResult.WarningMessage))
|
||||
{
|
||||
await LogWarning(riskResult.WarningMessage);
|
||||
}
|
||||
|
||||
if (riskResult.ShouldAutoClose && !string.IsNullOrEmpty(riskResult.EmergencyMessage))
|
||||
{
|
||||
await LogWarning(riskResult.EmergencyMessage);
|
||||
|
||||
var signalForAutoClose =
|
||||
Signals.FirstOrDefault(s => s.Identifier == positionForSignal.SignalIdentifier);
|
||||
if (signalForAutoClose != null)
|
||||
{
|
||||
await CloseTrade(signalForAutoClose, positionForSignal, positionForSignal.StopLoss,
|
||||
currentPrice, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -784,6 +849,20 @@ public class TradingBot : Bot, ITradingBot
|
||||
return false;
|
||||
}
|
||||
|
||||
// Synth-based pre-trade risk assessment
|
||||
if (Config.UseSynthApi)
|
||||
{
|
||||
var currentPrice = Config.IsForBacktest
|
||||
? OptimizedCandles.Last().Close
|
||||
: ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
|
||||
|
||||
if (!(await TradingService.AssessSynthPositionRiskAsync(Config.Ticker, signal.Direction, currentPrice,
|
||||
Config, Config.IsForBacktest)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check cooldown period and loss streak
|
||||
return await CheckCooldownPeriod(signal) && await CheckLossStreak(signal);
|
||||
}
|
||||
@@ -1030,7 +1109,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
// Add PnL (could be positive or negative)
|
||||
Config.BotTradingBalance += position.ProfitAndLoss.Realized;
|
||||
|
||||
|
||||
Logger.LogInformation(
|
||||
$"💰 **Balance Updated**\nNew bot trading balance: `${Config.BotTradingBalance:F2}`");
|
||||
}
|
||||
@@ -1150,7 +1229,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
public decimal GetTotalFees()
|
||||
{
|
||||
decimal totalFees = 0;
|
||||
|
||||
|
||||
foreach (var position in Positions.Where(p => p.Open.Price > 0 && p.Open.Quantity > 0))
|
||||
{
|
||||
totalFees += CalculatePositionFees(position);
|
||||
@@ -1167,22 +1246,22 @@ public class TradingBot : Bot, ITradingBot
|
||||
private decimal CalculatePositionFees(Position position)
|
||||
{
|
||||
decimal fees = 0;
|
||||
|
||||
|
||||
// Calculate position size in USD (leverage is already included in quantity calculation)
|
||||
var positionSizeUsd = position.Open.Price * position.Open.Quantity;
|
||||
|
||||
|
||||
// UI Fee: 0.1% of position size paid BOTH on opening AND closing
|
||||
var uiFeeRate = 0.001m; // 0.1%
|
||||
var uiFeeOpen = positionSizeUsd * uiFeeRate; // Fee paid on opening
|
||||
var uiFeeClose = positionSizeUsd * uiFeeRate; // Fee paid on closing
|
||||
var totalUiFees = uiFeeOpen + uiFeeClose; // Total: 0.2% of position size
|
||||
var uiFeeOpen = positionSizeUsd * uiFeeRate; // Fee paid on opening
|
||||
var uiFeeClose = positionSizeUsd * uiFeeRate; // Fee paid on closing
|
||||
var totalUiFees = uiFeeOpen + uiFeeClose; // Total: 0.2% of position size
|
||||
fees += totalUiFees;
|
||||
|
||||
|
||||
// Network Fee: $0.50 for opening position only
|
||||
// Closing is handled by oracle, so no network fee for closing
|
||||
var networkFeeForOpening = 0.50m;
|
||||
fees += networkFeeForOpening;
|
||||
|
||||
|
||||
return fees;
|
||||
}
|
||||
|
||||
@@ -1236,53 +1315,29 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
var data = new TradingBotBackup
|
||||
{
|
||||
Name = Name,
|
||||
BotType = Config.BotType,
|
||||
Config = Config,
|
||||
Signals = Signals,
|
||||
Positions = Positions,
|
||||
Timeframe = Config.Timeframe,
|
||||
Ticker = Config.Ticker,
|
||||
ScenarioName = Config.ScenarioName,
|
||||
AccountName = Config.AccountName,
|
||||
IsForWatchingOnly = Config.IsForWatchingOnly,
|
||||
WalletBalances = WalletBalances,
|
||||
MoneyManagement = Config.MoneyManagement,
|
||||
BotTradingBalance = Config.BotTradingBalance,
|
||||
StartupTime = StartupTime,
|
||||
CooldownPeriod = Config.CooldownPeriod,
|
||||
MaxLossStreak = Config.MaxLossStreak,
|
||||
MaxPositionTimeHours = Config.MaxPositionTimeHours ?? 0m,
|
||||
FlipOnlyWhenInProfit = Config.FlipOnlyWhenInProfit,
|
||||
CloseEarlyWhenProfitable = Config.CloseEarlyWhenProfitable,
|
||||
StartupTime = StartupTime
|
||||
};
|
||||
BotService.SaveOrUpdateBotBackup(User, Identifier, Config.BotType, Status, JsonConvert.SerializeObject(data));
|
||||
BotService.SaveOrUpdateBotBackup(User, Identifier, Status, JsonConvert.SerializeObject(data));
|
||||
}
|
||||
|
||||
public override void LoadBackup(BotBackup backup)
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<TradingBotBackup>(backup.Data);
|
||||
Config = new TradingBotConfig
|
||||
{
|
||||
AccountName = data.AccountName,
|
||||
MoneyManagement = data.MoneyManagement,
|
||||
Ticker = data.Ticker,
|
||||
ScenarioName = data.ScenarioName,
|
||||
Timeframe = data.Timeframe,
|
||||
IsForBacktest = false, // Always false when loading from backup
|
||||
IsForWatchingOnly = data.IsForWatchingOnly,
|
||||
BotTradingBalance = data.BotTradingBalance,
|
||||
BotType = data.BotType,
|
||||
CooldownPeriod = data.CooldownPeriod,
|
||||
MaxLossStreak = data.MaxLossStreak,
|
||||
MaxPositionTimeHours = data.MaxPositionTimeHours == 0m ? null : data.MaxPositionTimeHours,
|
||||
FlipOnlyWhenInProfit = data.FlipOnlyWhenInProfit,
|
||||
CloseEarlyWhenProfitable = data.CloseEarlyWhenProfitable,
|
||||
Name = data.Name
|
||||
};
|
||||
|
||||
Signals = data.Signals;
|
||||
Positions = data.Positions;
|
||||
WalletBalances = data.WalletBalances;
|
||||
// Load the configuration directly
|
||||
Config = data.Config;
|
||||
|
||||
// Ensure IsForBacktest is always false when loading from backup
|
||||
Config.IsForBacktest = false;
|
||||
|
||||
// Load runtime state
|
||||
Signals = data.Signals ?? new HashSet<Signal>();
|
||||
Positions = data.Positions ?? new List<Position>();
|
||||
WalletBalances = data.WalletBalances ?? new Dictionary<DateTime, decimal>();
|
||||
PreloadSince = data.StartupTime;
|
||||
Identifier = backup.Identifier;
|
||||
User = backup.User;
|
||||
@@ -1307,7 +1362,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
// Create a fake signal for manual position opening
|
||||
var signal = new Signal(Config.Ticker, direction, Confidence.Low, lastCandle, lastCandle.Date,
|
||||
TradingExchanges.GmxV2,
|
||||
IndicatorType.Stc, SignalType.Signal);
|
||||
IndicatorType.Stc, SignalType.Signal, "Manual Signal");
|
||||
signal.Status = SignalStatus.WaitingForPosition; // Ensure status is correct
|
||||
signal.User = Account.User; // Assign user
|
||||
|
||||
@@ -1433,7 +1488,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
|
||||
// If scenario changed, reload it
|
||||
var currentScenario = Scenario?.Name;
|
||||
var currentScenario = Config.Scenario?.Name;
|
||||
if (Config.ScenarioName != currentScenario)
|
||||
{
|
||||
LoadScenario(Config.ScenarioName);
|
||||
@@ -1485,29 +1540,36 @@ public class TradingBot : Bot, ITradingBot
|
||||
FlipOnlyWhenInProfit = Config.FlipOnlyWhenInProfit,
|
||||
FlipPosition = Config.FlipPosition,
|
||||
Name = Config.Name,
|
||||
CloseEarlyWhenProfitable = Config.CloseEarlyWhenProfitable
|
||||
CloseEarlyWhenProfitable = Config.CloseEarlyWhenProfitable,
|
||||
UseSynthApi = Config.UseSynthApi,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class TradingBotBackup
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public BotType BotType { get; set; }
|
||||
/// <summary>
|
||||
/// The complete trading bot configuration
|
||||
/// </summary>
|
||||
public TradingBotConfig Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Runtime state: Active signals for the bot
|
||||
/// </summary>
|
||||
public HashSet<Signal> Signals { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Runtime state: Open and closed positions for the bot
|
||||
/// </summary>
|
||||
public List<Position> Positions { get; set; }
|
||||
public Timeframe Timeframe { get; set; }
|
||||
public Ticker Ticker { get; set; }
|
||||
public string ScenarioName { get; set; }
|
||||
public string AccountName { get; set; }
|
||||
public bool IsForWatchingOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Runtime state: Historical wallet balances over time
|
||||
/// </summary>
|
||||
public Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
||||
public MoneyManagement MoneyManagement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Runtime state: When the bot was started
|
||||
/// </summary>
|
||||
public DateTime StartupTime { get; set; }
|
||||
public decimal BotTradingBalance { get; set; }
|
||||
public int CooldownPeriod { get; set; }
|
||||
public int MaxLossStreak { get; set; }
|
||||
public decimal MaxPositionTimeHours { get; set; }
|
||||
public bool FlipOnlyWhenInProfit { get; set; }
|
||||
public bool CloseEarlyWhenProfitable { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user