Files
aitrade/pkg/app/trader/stoploss.go
T
kaedwen ea141eb012
Build and Push Docker Image / build-and-push (push) Failing after 1m41s
initial
2026-07-02 20:09:44 +02:00

128 lines
3.1 KiB
Go

package trader
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/pheinrich/aitrade/pkg/app/client"
"github.com/pheinrich/aitrade/pkg/db"
"github.com/pheinrich/aitrade/pkg/model"
)
type StopLossManager struct {
client *client.IBClient
tradeRepo *db.TradeRepository
logger *slog.Logger
enabled bool
}
func NewStopLossManager(client *client.IBClient, tradeRepo *db.TradeRepository, enabled bool, logger *slog.Logger) *StopLossManager {
return &StopLossManager{
client: client,
tradeRepo: tradeRepo,
logger: logger,
enabled: enabled,
}
}
func (s *StopLossManager) CreateStopLoss(ctx context.Context, trade *model.Trade, stopLossPercent float64) error {
if !s.enabled || trade.ExecutedPrice == nil {
return nil
}
executedPrice := *trade.ExecutedPrice
stopPrice := executedPrice * (1.0 - stopLossPercent/100.0)
s.logger.Info("creating stop-loss order",
slog.Int64("trade_id", trade.ID),
slog.String("symbol", trade.Symbol),
slog.Float64("executed_price", executedPrice),
slog.Float64("stop_price", stopPrice),
)
order := &client.Order{
Symbol: trade.Symbol,
Action: model.ActionSell, // Stop-loss is always a sell
Quantity: trade.Quantity,
OrderType: "STP",
StopPrice: &stopPrice,
}
orderID, err := s.client.PlaceOrder(ctx, order)
if err != nil {
return fmt.Errorf("failed to place stop-loss order: %w", err)
}
// Store stop-loss details
trade.StopLossPrice = &stopPrice
trade.IBOrderID = &orderID
if err := s.tradeRepo.Update(ctx, trade); err != nil {
return fmt.Errorf("failed to update trade with stop-loss: %w", err)
}
return nil
}
func (s *StopLossManager) MonitorStopLoss(ctx context.Context, trade *model.Trade) error {
if !s.enabled || trade.StopLossPrice == nil {
return nil
}
// In a real implementation, this would check the current market price
// and trigger the stop-loss if the price has fallen below the threshold
// For now, this is a placeholder
s.logger.Debug("monitoring stop-loss",
slog.Int64("trade_id", trade.ID),
slog.String("symbol", trade.Symbol),
slog.Float64("stop_price", *trade.StopLossPrice),
)
return nil
}
func (s *StopLossManager) Run(ctx context.Context) error {
if !s.enabled {
s.logger.Info("stop-loss manager disabled")
return nil
}
s.logger.Info("stop-loss manager starting")
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
s.logger.Info("stop-loss manager stopping")
return ctx.Err()
case <-ticker.C:
if err := s.monitorAllActiveTrades(ctx); err != nil {
s.logger.Error("failed to monitor stop-losses", slog.Any("error", err))
}
}
}
}
func (s *StopLossManager) monitorAllActiveTrades(ctx context.Context) error {
trades, err := s.tradeRepo.GetActiveTrades(ctx)
if err != nil {
return fmt.Errorf("failed to get active trades: %w", err)
}
for _, trade := range trades {
if err := s.MonitorStopLoss(ctx, trade); err != nil {
s.logger.Error("failed to monitor stop-loss for trade",
slog.Int64("trade_id", trade.ID),
slog.Any("error", err),
)
}
}
return nil
}