docker files fixes from liaqat
This commit is contained in:
111
src/Managing.Application/Bots/Base/BotFactory.cs
Normal file
111
src/Managing.Application/Bots/Base/BotFactory.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Workflows;
|
||||
using Managing.Domain.Bots;
|
||||
|
||||
namespace Managing.Application.Bots.Base
|
||||
{
|
||||
public class BotFactory : IBotFactory
|
||||
{
|
||||
private readonly IMoneyManagementService _moneyManagementService;
|
||||
private readonly IExchangeService _exchangeService;
|
||||
private readonly IMessengerService _messengerService;
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly ILogger<TradingBot> _tradingBotLogger;
|
||||
private readonly ITradingService _tradingService;
|
||||
|
||||
public BotFactory(
|
||||
IExchangeService exchangeService,
|
||||
ILogger<TradingBot> tradingBotLogger,
|
||||
IMoneyManagementService moneyManagementService,
|
||||
IMessengerService messengerService,
|
||||
IAccountService accountService,
|
||||
ITradingService tradingService)
|
||||
{
|
||||
_tradingBotLogger = tradingBotLogger;
|
||||
_exchangeService = exchangeService;
|
||||
_moneyManagementService = moneyManagementService;
|
||||
_messengerService = messengerService;
|
||||
_accountService = accountService;
|
||||
_tradingService = tradingService;
|
||||
}
|
||||
|
||||
IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow)
|
||||
{
|
||||
return new SimpleBot(botName, _tradingBotLogger, workflow);
|
||||
}
|
||||
|
||||
ITradingBot IBotFactory.CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
|
||||
{
|
||||
return new ScalpingBot(
|
||||
accountName,
|
||||
moneyManagement,
|
||||
name,
|
||||
scenario,
|
||||
_exchangeService,
|
||||
ticker,
|
||||
_tradingService,
|
||||
_tradingBotLogger,
|
||||
interval,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
isForWatchingOnly: isForWatchingOnly);
|
||||
}
|
||||
|
||||
ITradingBot IBotFactory.CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
|
||||
{
|
||||
return new ScalpingBot(
|
||||
accountName,
|
||||
moneyManagement,
|
||||
"BacktestBot",
|
||||
scenario,
|
||||
_exchangeService,
|
||||
ticker,
|
||||
_tradingService,
|
||||
_tradingBotLogger,
|
||||
interval,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
true,
|
||||
isForWatchingOnly);
|
||||
}
|
||||
|
||||
public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
|
||||
{
|
||||
return new FlippingBot(
|
||||
accountName,
|
||||
moneyManagement,
|
||||
name,
|
||||
scenario,
|
||||
_exchangeService,
|
||||
ticker,
|
||||
_tradingService,
|
||||
_tradingBotLogger,
|
||||
interval,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
isForWatchingOnly: isForWatchingOnly);
|
||||
}
|
||||
|
||||
public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
|
||||
{
|
||||
return new FlippingBot(
|
||||
accountName,
|
||||
moneyManagement,
|
||||
"BacktestBot",
|
||||
scenario,
|
||||
_exchangeService,
|
||||
ticker,
|
||||
_tradingService,
|
||||
_tradingBotLogger,
|
||||
interval,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
true,
|
||||
isForWatchingOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/Managing.Application/Bots/FlippingBot.cs
Normal file
49
src/Managing.Application/Bots/FlippingBot.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
|
||||
namespace Managing.Application.Bots
|
||||
{
|
||||
public class FlippingBot : TradingBot
|
||||
{
|
||||
public FlippingBot(string accountName,
|
||||
MoneyManagement moneyManagement,
|
||||
string name,
|
||||
string scenario,
|
||||
IExchangeService exchangeService,
|
||||
Ticker ticker,
|
||||
ITradingService tradingService,
|
||||
ILogger<TradingBot> logger,
|
||||
Timeframe timeframe,
|
||||
IAccountService accountService,
|
||||
IMessengerService messengerService,
|
||||
bool isForBacktest = false,
|
||||
bool isForWatchingOnly = false)
|
||||
: base(accountName,
|
||||
moneyManagement,
|
||||
name,
|
||||
ticker,
|
||||
scenario,
|
||||
exchangeService,
|
||||
logger,
|
||||
tradingService,
|
||||
timeframe,
|
||||
accountService,
|
||||
messengerService,
|
||||
isForBacktest,
|
||||
isForWatchingOnly,
|
||||
flipPosition: true)
|
||||
{
|
||||
BotType = BotType.FlippingBot;
|
||||
Start();
|
||||
}
|
||||
|
||||
public sealed override void Start()
|
||||
{
|
||||
Logger.LogInformation($"{Name} - Bot Started");
|
||||
base.Start();
|
||||
Logger.LogInformation($"Starting {Name} bot - Status : {Status}");
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/Managing.Application/Bots/ScalpingBot.cs
Normal file
48
src/Managing.Application/Bots/ScalpingBot.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
|
||||
namespace Managing.Application.Bots
|
||||
{
|
||||
public class ScalpingBot : TradingBot
|
||||
{
|
||||
public ScalpingBot(string accountName,
|
||||
MoneyManagement moneyManagement,
|
||||
string name,
|
||||
string scenario,
|
||||
IExchangeService exchangeService,
|
||||
Ticker ticker,
|
||||
ITradingService tradingService,
|
||||
ILogger<TradingBot> logger,
|
||||
Timeframe timeframe,
|
||||
IAccountService accountService,
|
||||
IMessengerService messengerService,
|
||||
bool isForBacktest = false,
|
||||
bool isForWatchingOnly = false)
|
||||
: base(accountName,
|
||||
moneyManagement,
|
||||
name,
|
||||
ticker,
|
||||
scenario,
|
||||
exchangeService,
|
||||
logger,
|
||||
tradingService,
|
||||
timeframe,
|
||||
accountService,
|
||||
messengerService,
|
||||
isForBacktest,
|
||||
isForWatchingOnly)
|
||||
{
|
||||
BotType = BotType.ScalpingBot;
|
||||
Start();
|
||||
}
|
||||
|
||||
public sealed override void Start()
|
||||
{
|
||||
Logger.LogInformation($"{Name} - Bot Started");
|
||||
base.Start();
|
||||
Logger.LogInformation($"Starting {Name} bot - Status : {Status}");
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/Managing.Application/Bots/SimpleBot.cs
Normal file
39
src/Managing.Application/Bots/SimpleBot.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Workflows;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Managing.Application.Bots
|
||||
{
|
||||
public class SimpleBot : Bot
|
||||
{
|
||||
public readonly ILogger<TradingBot> Logger;
|
||||
private readonly Workflow _workflow;
|
||||
|
||||
public SimpleBot(string name, ILogger<TradingBot> logger, Workflow workflow) : base(name)
|
||||
{
|
||||
Logger = logger;
|
||||
_workflow = workflow;
|
||||
Interval = 100;
|
||||
Start();
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
Task.Run(() => InitWorker(Run));
|
||||
|
||||
base.Start();
|
||||
}
|
||||
|
||||
public async Task Run()
|
||||
{
|
||||
await Task.Run(
|
||||
async () =>
|
||||
{
|
||||
Logger.LogInformation(Identifier);
|
||||
Logger.LogInformation(DateTime.Now.ToString());
|
||||
await _workflow.Execute();
|
||||
Logger.LogInformation("__________________________________________________");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
651
src/Managing.Application/Bots/TradingBot.cs
Normal file
651
src/Managing.Application/Bots/TradingBot.cs
Normal file
@@ -0,0 +1,651 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Trading;
|
||||
using Managing.Application.Trading.Commands;
|
||||
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;
|
||||
using Managing.Domain.Trades;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Bots;
|
||||
|
||||
public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
public readonly ILogger<TradingBot> Logger;
|
||||
public readonly IExchangeService ExchangeService;
|
||||
public readonly IMessengerService MessengerService;
|
||||
public readonly IAccountService AccountService;
|
||||
private readonly ITradingService TradingService;
|
||||
|
||||
public Account Account { get; set; }
|
||||
public HashSet<IStrategy> Strategies { get; set; }
|
||||
public HashSet<Candle> Candles { get; set; }
|
||||
public HashSet<Signal> Signals { get; set; }
|
||||
public List<Position> Positions { get; set; }
|
||||
public Ticker Ticker { get; set; }
|
||||
public string Scenario { get; set; }
|
||||
public string AccountName { get; set; }
|
||||
public MoneyManagement MoneyManagement { get; set; }
|
||||
public Timeframe Timeframe { get; set; }
|
||||
public bool IsForBacktest { get; set; }
|
||||
public DateTime PreloadSince { get; set; }
|
||||
public bool IsForWatchingOnly { get; set; }
|
||||
public bool FlipPosition { get; set; }
|
||||
public int PreloadedCandlesCount { get; set; }
|
||||
public BotType BotType { get; set; }
|
||||
public decimal Fee { get; set; }
|
||||
public Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
||||
|
||||
public TradingBot(
|
||||
string accountName,
|
||||
MoneyManagement moneyManagement,
|
||||
string name,
|
||||
Ticker ticker,
|
||||
string scenario,
|
||||
IExchangeService exchangeService,
|
||||
ILogger<TradingBot> logger,
|
||||
ITradingService tradingService,
|
||||
Timeframe timeframe,
|
||||
IAccountService accountService,
|
||||
IMessengerService messengerService,
|
||||
bool isForBacktest = false,
|
||||
bool isForWatchingOnly = false,
|
||||
bool flipPosition = false)
|
||||
: base(name)
|
||||
{
|
||||
ExchangeService = exchangeService;
|
||||
AccountService = accountService;
|
||||
MessengerService = messengerService;
|
||||
TradingService = tradingService;
|
||||
|
||||
IsForWatchingOnly = isForWatchingOnly;
|
||||
FlipPosition = flipPosition;
|
||||
AccountName = accountName;
|
||||
MoneyManagement = moneyManagement;
|
||||
Ticker = ticker;
|
||||
Scenario = scenario;
|
||||
Timeframe = timeframe;
|
||||
IsForBacktest = isForBacktest;
|
||||
Logger = logger;
|
||||
|
||||
Strategies = new HashSet<IStrategy>();
|
||||
Signals = new HashSet<Signal>();
|
||||
Candles = new HashSet<Candle>();
|
||||
Positions = new List<Position>();
|
||||
WalletBalances = new Dictionary<DateTime, decimal>();
|
||||
|
||||
if (!isForBacktest)
|
||||
{
|
||||
Interval = CandleExtensions.GetIntervalFromTimeframe(timeframe);
|
||||
PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(timeframe);
|
||||
}
|
||||
}
|
||||
|
||||
public override async void Start()
|
||||
{
|
||||
base.Start();
|
||||
await LoadAccount();
|
||||
|
||||
if (!IsForBacktest)
|
||||
{
|
||||
LoadScenario();
|
||||
await PreloadCandles();
|
||||
await CancelAllOrders();
|
||||
await MessengerService.SendMessage($"Hi everyone, I'm going to run {Name}. \nI will send a message here everytime a signal is triggered by the {string.Join(",", Strategies.Select(s => s.Name))} strategies.");
|
||||
await InitWorker(Run);
|
||||
}
|
||||
|
||||
Fee = TradingService.GetFee(Account, IsForBacktest);
|
||||
}
|
||||
|
||||
private async Task LoadAccount()
|
||||
{
|
||||
var account = await AccountService.GetAccount(AccountName, false, true);
|
||||
if (account == null)
|
||||
{
|
||||
Logger.LogWarning($"No account found for this {AccountName}");
|
||||
Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
Account = account;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadScenario()
|
||||
{
|
||||
var scenario = TradingService.GetScenarioByName(Scenario);
|
||||
if (scenario == null)
|
||||
{
|
||||
Logger.LogWarning("No scenario found for this scenario name");
|
||||
Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadStrategies(ScenarioHelpers.GetStrategiesFromScenario(scenario));
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadStrategies(IEnumerable<IStrategy> strategies)
|
||||
{
|
||||
foreach (var strategy in strategies)
|
||||
{
|
||||
Strategies.Add(strategy);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Run()
|
||||
{
|
||||
Logger.LogInformation($"____________________{Name}____________________");
|
||||
Logger.LogInformation($"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}");
|
||||
|
||||
var previousCandleCount = Candles.Count;
|
||||
|
||||
if (!IsForBacktest)
|
||||
await UpdateCandles();
|
||||
|
||||
if (Candles.Count > previousCandleCount || IsForBacktest)
|
||||
await UpdateSignals(Candles);
|
||||
else
|
||||
Logger.LogInformation($"No need to update signals for {Ticker}");
|
||||
|
||||
if (!IsForWatchingOnly)
|
||||
await ManagePositions();
|
||||
|
||||
await UpdateWalletBalances();
|
||||
Logger.LogInformation($"Candles : {Candles.Count}");
|
||||
Logger.LogInformation($"Signals : {Signals.Count}");
|
||||
Logger.LogInformation($"ExecutionCount : {ExecutionCount}");
|
||||
Logger.LogInformation($"Positions : {Positions.Count}");
|
||||
Logger.LogInformation("__________________________________________________");
|
||||
}
|
||||
|
||||
private async Task PreloadCandles()
|
||||
{
|
||||
var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe);
|
||||
|
||||
foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
|
||||
{
|
||||
if (!Candles.Any(c => c.Date == candle.Date))
|
||||
{
|
||||
Candles.Add(candle);
|
||||
await UpdateSignals(Candles);
|
||||
}
|
||||
}
|
||||
|
||||
PreloadedCandlesCount = Candles.Count();
|
||||
}
|
||||
|
||||
private async Task UpdateSignals(HashSet<Candle> candles)
|
||||
{
|
||||
var signal = TradingBox.GetSignal(candles, Strategies, Signals);
|
||||
|
||||
if (signal == null) return;
|
||||
|
||||
await AddSignal(signal);
|
||||
}
|
||||
|
||||
|
||||
private async Task AddSignal(Signal signal)
|
||||
{
|
||||
Signals.Add(signal);
|
||||
|
||||
if (!IsForBacktest)
|
||||
TradingService.InsertSignal(signal);
|
||||
|
||||
if (IsForWatchingOnly || (ExecutionCount < 1 && !IsForBacktest))
|
||||
signal.Status = SignalStatus.Expired;
|
||||
|
||||
var signalText = $"{Scenario} trigger a signal. Signal told you " +
|
||||
$"to {signal.Direction} {Ticker} on {Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}";
|
||||
|
||||
Logger.LogInformation(signalText);
|
||||
|
||||
if (IsForWatchingOnly && !IsForBacktest && ExecutionCount > 0)
|
||||
{
|
||||
await MessengerService.SendSignal(signalText, Account.Exchange, Ticker, signal.Direction, Timeframe);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task UpdateCandles()
|
||||
{
|
||||
if (Candles.Count == 0 || ExecutionCount == 0)
|
||||
return;
|
||||
|
||||
var lastCandle = Candles.Last();
|
||||
var newCandle = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, lastCandle.Date, Timeframe);
|
||||
|
||||
foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
|
||||
{
|
||||
Candles.Add(candle);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ManagePositions()
|
||||
{
|
||||
if (!IsForBacktest && ExecutionCount < 1)
|
||||
return;
|
||||
|
||||
// Update position
|
||||
foreach (var signal in Signals.Where(s => s.Status == SignalStatus.PositionOpen))
|
||||
{
|
||||
var positionForSignal = Positions.FirstOrDefault(p => p.SignalIdentifier == signal.Identifier);
|
||||
await UpdatePosition(signal, positionForSignal);
|
||||
}
|
||||
|
||||
// Open position for signal waiting for a position open
|
||||
foreach (var signal in Signals.Where(s => s.Status == SignalStatus.WaitingForPosition))
|
||||
{
|
||||
Task.Run(() => OpenPosition(signal)).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateWalletBalances()
|
||||
{
|
||||
if (WalletBalances.Count == 0)
|
||||
{
|
||||
WalletBalances.Add(Candles.LastOrDefault().Date, await ExchangeService.GetBalance(Account, IsForBacktest));
|
||||
}
|
||||
else if (!WalletBalances.Any(w => w.Key == Candles.LastOrDefault().Date))
|
||||
{
|
||||
var walletBalance = WalletBalances.FirstOrDefault().Value + GetProfitAndLoss();
|
||||
WalletBalances.Add(Candles.LastOrDefault().Date, walletBalance);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdatePosition(Signal signal, Position positionForSignal)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInformation($"Updating position {positionForSignal.SignalIdentifier}");
|
||||
|
||||
var position = IsForBacktest ? positionForSignal : TradingService.GetPositionByIdentifier(positionForSignal.Identifier);
|
||||
|
||||
if (position.Status == (PositionStatus.Finished | PositionStatus.Flipped))
|
||||
{
|
||||
await HandleClosedPosition(positionForSignal);
|
||||
return;
|
||||
}
|
||||
else if (position.Status == PositionStatus.Filled)
|
||||
{
|
||||
// For backtesting or force close if not executed on exchange :
|
||||
// check if position is still open
|
||||
// Check status, if still open update the status of the position
|
||||
var lastCandle = IsForBacktest ? Candles.Last() : ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow);
|
||||
|
||||
if (positionForSignal.OriginDirection == TradeDirection.Long)
|
||||
{
|
||||
if (positionForSignal.StopLoss.Price >= lastCandle.Low)
|
||||
{
|
||||
await LogInformation($"Closing position - SL {positionForSignal.StopLoss.Price} >= Price {lastCandle.Low}");
|
||||
await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, positionForSignal.StopLoss.Price, true);
|
||||
positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
|
||||
}
|
||||
else if (positionForSignal.TakeProfit1.Price <= lastCandle.High
|
||||
&& positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
|
||||
{
|
||||
await LogInformation($"Closing position - TP1 {positionForSignal.TakeProfit1.Price} <= Price {lastCandle.High}");
|
||||
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
|
||||
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled);
|
||||
}
|
||||
else if (positionForSignal.TakeProfit2?.Price <= lastCandle.High)
|
||||
{
|
||||
await LogInformation($"Closing position - TP2 {positionForSignal.TakeProfit2.Price} <= Price {lastCandle.High}");
|
||||
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, positionForSignal.TakeProfit2.Price, true);
|
||||
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation($"Position {signal.Identifier} don't need to be update. Position still opened");
|
||||
}
|
||||
}
|
||||
|
||||
if (positionForSignal.OriginDirection == TradeDirection.Short)
|
||||
{
|
||||
if (positionForSignal.StopLoss.Price <= lastCandle.High)
|
||||
{
|
||||
await LogInformation($"Closing position - SL {positionForSignal.StopLoss.Price} <= Price {lastCandle.High}");
|
||||
await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, positionForSignal.StopLoss.Price, true);
|
||||
positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
|
||||
}
|
||||
else if (positionForSignal.TakeProfit1.Price >= lastCandle.Low
|
||||
&& positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
|
||||
{
|
||||
await LogInformation($"Closing position - TP1 {positionForSignal.TakeProfit1.Price} >= Price {lastCandle.Low}");
|
||||
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
|
||||
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled);
|
||||
}
|
||||
else if (positionForSignal.TakeProfit2?.Price >= lastCandle.Low)
|
||||
{
|
||||
await LogInformation($"Closing position - TP2 {positionForSignal.TakeProfit2.Price} >= Price {lastCandle.Low}");
|
||||
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, positionForSignal.TakeProfit2.Price, true);
|
||||
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation($"Position {signal.Identifier} don't need to be update. Position still opened");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (position.Status == (PositionStatus.Rejected | PositionStatus.Canceled))
|
||||
{
|
||||
await LogWarning($"Open position trade is rejected for signal {signal.Identifier}");
|
||||
// if position is not open
|
||||
// Re-open the trade for the signal only if signal still up
|
||||
//if (signal.Status == SignalStatus.PositionOpen)
|
||||
//{
|
||||
// Logger.LogInformation($"Try to re-open position");
|
||||
// OpenPosition(signal);
|
||||
//}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, ex.Message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task OpenPosition(Signal signal)
|
||||
{
|
||||
// Check if a position is already open
|
||||
Logger.LogInformation($"Opening position for {signal.Identifier}");
|
||||
|
||||
var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
|
||||
&& p.SignalIdentifier != signal.Identifier);
|
||||
|
||||
var lastPrice = IsForBacktest ? Candles.Last().Close : ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow);
|
||||
|
||||
// If position open
|
||||
if (openedPosition != null)
|
||||
{
|
||||
var previousSignal = Signals.First(s => s.Identifier == openedPosition.SignalIdentifier);
|
||||
|
||||
// Check if signal is the opposite side => flip the position
|
||||
if (openedPosition.OriginDirection == signal.Direction)
|
||||
{
|
||||
// An operation is already open for the same direction
|
||||
await LogInformation($"Signal {signal.Identifier} try to open a position but {previousSignal.Identifier} is already open for the same direction");
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
}
|
||||
else
|
||||
{
|
||||
// An operation is already open for the opposite direction
|
||||
// ==> Flip the position
|
||||
if (FlipPosition)
|
||||
{
|
||||
await LogInformation("Try to flip the position because of an opposite direction signal");
|
||||
await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
|
||||
await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped);
|
||||
await OpenPosition(signal);
|
||||
await LogInformation($"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$");
|
||||
}
|
||||
else
|
||||
{
|
||||
await LogWarning($"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped.");
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!CanOpenPosition(signal))
|
||||
{
|
||||
await LogInformation("Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position");
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
return;
|
||||
}
|
||||
|
||||
await LogInformation($"Open position - Date: {signal.Date:T} - SignalIdentifier : {signal.Identifier} - Strategie : {signal.StrategyType}");
|
||||
|
||||
try
|
||||
{
|
||||
var command = new OpenPositionRequest(
|
||||
AccountName,
|
||||
MoneyManagement,
|
||||
signal.Direction,
|
||||
Ticker,
|
||||
PositionInitiator.Bot,
|
||||
signal.Date,
|
||||
IsForBacktest,
|
||||
lastPrice,
|
||||
balance: WalletBalances.LastOrDefault().Value,
|
||||
fee: Fee);
|
||||
|
||||
var position = await new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService)
|
||||
.Handle(command);
|
||||
|
||||
if (position != null)
|
||||
{
|
||||
if (position.Open.Status != TradeStatus.Cancelled)
|
||||
{
|
||||
position.SignalIdentifier = signal.Identifier;
|
||||
Positions.Add(position);
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen);
|
||||
|
||||
if (!IsForBacktest)
|
||||
{
|
||||
await MessengerService.SendPosition(position);
|
||||
}
|
||||
|
||||
Logger.LogInformation($"Position requested");
|
||||
}
|
||||
else
|
||||
{
|
||||
await SetPositionStatus(signal.Identifier, PositionStatus.Rejected);
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
await LogWarning($"Cannot open trade : {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanOpenPosition(Signal signal)
|
||||
{
|
||||
if (Positions.Count == 0)
|
||||
return true;
|
||||
|
||||
var lastPosition = Positions.LastOrDefault(p => p.IsFinished()
|
||||
&& p.SignalIdentifier != signal.Identifier
|
||||
&& p.ProfitAndLoss.Realized < 0
|
||||
&& p.OriginDirection == signal.Direction);
|
||||
|
||||
if (lastPosition == null)
|
||||
return true;
|
||||
|
||||
var tenCandleAgo = Candles.TakeLast(10).First();
|
||||
var positionSignal = Signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier);
|
||||
|
||||
return positionSignal.Date < tenCandleAgo.Date;
|
||||
}
|
||||
|
||||
private async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice, bool tradeClosingPosition = false)
|
||||
{
|
||||
if (position.TakeProfit2 != null && position.TakeProfit1.Status == TradeStatus.Filled && tradeToClose.TradeType == TradeType.StopMarket)
|
||||
{
|
||||
// If trade is the 2nd Take profit
|
||||
tradeToClose.Quantity = position.TakeProfit2.Quantity;
|
||||
}
|
||||
|
||||
await LogInformation($"Trying to close trade {Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " +
|
||||
$"- Closing Position : {tradeClosingPosition}");
|
||||
|
||||
// Get status of position before closing it. The position might be already close by the exchange
|
||||
if (!IsForBacktest && await ExchangeService.GetQuantityInPosition(Account, Ticker) == 0)
|
||||
{
|
||||
Logger.LogInformation($"Trade already close on exchange");
|
||||
await HandleClosedPosition(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
var command = new ClosePositionCommand(position, lastPrice);
|
||||
try
|
||||
{
|
||||
var closedPosition = await (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService))
|
||||
.Handle(command);
|
||||
|
||||
if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped))
|
||||
{
|
||||
if (tradeClosingPosition)
|
||||
{
|
||||
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
|
||||
}
|
||||
|
||||
await HandleClosedPosition(closedPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Wrong position status : {closedPosition.Status}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await LogWarning($"Position {signal.Identifier} not closed : {ex.Message}");
|
||||
|
||||
if (position.Status == (PositionStatus.Canceled | PositionStatus.Rejected))
|
||||
{
|
||||
// Trade close on exchange => Should close trade manually
|
||||
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleClosedPosition(Position position)
|
||||
{
|
||||
if (Positions.Any(p => p.Identifier == position.Identifier))
|
||||
{
|
||||
var previousPosition = Positions.First(p => p.Identifier == position.Identifier);
|
||||
var positionIndex = Positions.IndexOf(previousPosition);
|
||||
position.SignalIdentifier = previousPosition.SignalIdentifier;
|
||||
Positions[positionIndex] = position;
|
||||
SetSignalStatus(position.SignalIdentifier, SignalStatus.Expired);
|
||||
Logger.LogInformation($"Position {position.SignalIdentifier} type correctly close. Pnl on position : {position.ProfitAndLoss.Realized}");
|
||||
}
|
||||
else
|
||||
{
|
||||
await LogWarning("Weird things happen - Trying to update position status, but no position found");
|
||||
}
|
||||
|
||||
if (!IsForBacktest)
|
||||
{
|
||||
await MessengerService.SendClosingPosition(position);
|
||||
}
|
||||
|
||||
await CancelAllOrders();
|
||||
}
|
||||
|
||||
private async Task CancelAllOrders()
|
||||
{
|
||||
if (!IsForBacktest)
|
||||
{
|
||||
try
|
||||
{
|
||||
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker);
|
||||
Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Todo handle exception from evm
|
||||
Logger.LogError(ex, "Error during cancelOrders");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetPositionStatus(string signalIdentifier, PositionStatus positionStatus)
|
||||
{
|
||||
await LogInformation($"Position {signalIdentifier} is now {positionStatus}");
|
||||
Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
|
||||
SetSignalStatus(signalIdentifier, SignalStatus.Expired);
|
||||
}
|
||||
|
||||
private void SetSignalStatus(string signalIdentifier, SignalStatus signalStatus)
|
||||
{
|
||||
if (Signals.Any(s => s.Identifier == signalIdentifier))
|
||||
{
|
||||
Signals.First(s => s.Identifier == signalIdentifier).Status = signalStatus;
|
||||
Logger.LogInformation($"Signal {signalIdentifier} is now {signalStatus}");
|
||||
}
|
||||
}
|
||||
|
||||
public int GetWinRate()
|
||||
{
|
||||
var succeededPositions = Positions.Where(p => p.IsFinished()).Count(p => p.ProfitAndLoss?.Realized > 0);
|
||||
var total = Positions.Where(p => p.IsFinished()).Count();
|
||||
|
||||
if (total == 0)
|
||||
return 0;
|
||||
|
||||
return (succeededPositions * 100) / total;
|
||||
}
|
||||
|
||||
public decimal GetProfitAndLoss()
|
||||
{
|
||||
var pnl = Positions.Where(p => p.ProfitAndLoss != null).Sum(p => p.ProfitAndLoss.Realized);
|
||||
return pnl - GetTotalFees();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the total fees paid by the trading bot for each position.
|
||||
/// </summary>
|
||||
/// <returns>Returns the total fees paid as a decimal value.</returns>
|
||||
public decimal GetTotalFees()
|
||||
{
|
||||
decimal fees = 0;
|
||||
foreach (var position in Positions.Where(p => p.Open.Fee > 0))
|
||||
{
|
||||
fees += position.Open.Fee;
|
||||
fees += position.StopLoss.Status == TradeStatus.Filled ? position.StopLoss.Fee : 0;
|
||||
fees += position.TakeProfit1.Status == TradeStatus.Filled ? position.TakeProfit1.Fee : 0;
|
||||
|
||||
if (position.IsFinished() &&
|
||||
position.StopLoss.Status != TradeStatus.Filled && position.TakeProfit1.Status != TradeStatus.Filled)
|
||||
fees += position.Open.Fee;
|
||||
|
||||
if (position.TakeProfit2 != null)
|
||||
fees += position.TakeProfit2.Fee;
|
||||
}
|
||||
|
||||
return fees;
|
||||
}
|
||||
|
||||
public async Task ToggleIsForWatchOnly()
|
||||
{
|
||||
IsForWatchingOnly = (!IsForWatchingOnly);
|
||||
await LogInformation($"Watch only toggle for bot : {Name} - Watch only : {IsForWatchingOnly}");
|
||||
}
|
||||
|
||||
private async Task LogInformation(string message)
|
||||
{
|
||||
Logger.LogInformation(message);
|
||||
await SendTradeMessage(message);
|
||||
}
|
||||
|
||||
private async Task LogWarning(string message)
|
||||
{
|
||||
Logger.LogWarning(message);
|
||||
await SendTradeMessage(message, true);
|
||||
}
|
||||
|
||||
private async Task SendTradeMessage(string message, bool isBadBehavior = false)
|
||||
{
|
||||
if (!IsForBacktest)
|
||||
{
|
||||
await MessengerService.SendTradeMessage(message, isBadBehavior);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user