102 lines
2.6 KiB
Go
102 lines
2.6 KiB
Go
package strategy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pheinrich/aitrade/pkg/model"
|
|
)
|
|
|
|
type DefensiveStrategy struct {
|
|
stopLossEnabled bool
|
|
stopLossPercent float64
|
|
}
|
|
|
|
func NewDefensiveStrategy(stopLossEnabled bool, stopLossPercent float64) *DefensiveStrategy {
|
|
return &DefensiveStrategy{
|
|
stopLossEnabled: stopLossEnabled,
|
|
stopLossPercent: stopLossPercent,
|
|
}
|
|
}
|
|
|
|
func (s *DefensiveStrategy) Name() string {
|
|
return "defensive"
|
|
}
|
|
|
|
func (s *DefensiveStrategy) GetRiskParams() RiskParameters {
|
|
return RiskParameters{
|
|
MaxParallelTrades: 2,
|
|
MaxTradesPerHour: 3,
|
|
PositionSizePercent: 1.5, // 1-2% of capital
|
|
StopLossPercent: 2.0,
|
|
}
|
|
}
|
|
|
|
func (s *DefensiveStrategy) Analyze(ctx context.Context, market *MarketData, news []*model.NewsArticle) (*TradeSignal, error) {
|
|
// Defensive strategy: Only trade on strong positive sentiment
|
|
// with multiple confirming news articles
|
|
|
|
positiveCount := 0
|
|
negativeCount := 0
|
|
totalSentiment := 0.0
|
|
|
|
for _, article := range news {
|
|
if article.SentimentScore == nil {
|
|
continue
|
|
}
|
|
|
|
// Check if article mentions this symbol
|
|
if !strings.Contains(strings.ToUpper(article.Symbols), market.Symbol) {
|
|
continue
|
|
}
|
|
|
|
sentiment := *article.SentimentScore
|
|
totalSentiment += sentiment
|
|
|
|
if article.SentimentLabel == "positive" {
|
|
positiveCount++
|
|
} else if article.SentimentLabel == "negative" {
|
|
negativeCount++
|
|
}
|
|
}
|
|
|
|
// Defensive: Need at least 3 positive articles and no negative ones
|
|
if positiveCount < 3 || negativeCount > 0 {
|
|
return nil, nil // No trade signal
|
|
}
|
|
|
|
avgSentiment := totalSentiment / float64(len(news))
|
|
|
|
// Strong positive sentiment required (>0.5)
|
|
if avgSentiment < 0.5 {
|
|
return nil, nil
|
|
}
|
|
|
|
return &TradeSignal{
|
|
Symbol: market.Symbol,
|
|
Action: model.ActionBuy,
|
|
Quantity: 0, // Will be calculated by trader
|
|
Confidence: avgSentiment * 0.8, // Conservative confidence
|
|
Reasoning: fmt.Sprintf("Defensive: %d positive news articles, avg sentiment %.2f", positiveCount, avgSentiment),
|
|
}, nil
|
|
}
|
|
|
|
// CalculatePositionSize - Defensive strategy uses conservative sizing with confidence scaling
|
|
func (s *DefensiveStrategy) CalculatePositionSize(price float64, confidence float64, availableCapital float64) int {
|
|
basePositionPercent := s.GetRiskParams().PositionSizePercent
|
|
|
|
// Defensive: More conservative confidence scaling (0.3 - 0.8x)
|
|
confidenceMultiplier := 0.3 + (confidence * 0.5)
|
|
adjustedPercent := basePositionPercent * confidenceMultiplier
|
|
|
|
positionValue := availableCapital * (adjustedPercent / 100.0)
|
|
quantity := int(positionValue / price)
|
|
|
|
if quantity < 1 {
|
|
return 1
|
|
}
|
|
|
|
return quantity
|
|
}
|