mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-08-07 01:33:52 +00:00
194 lines
6.1 KiB
Go
194 lines
6.1 KiB
Go
package msn
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
// News categories
|
|
const (
|
|
CategoryEarnings = "earnings"
|
|
CategoryDividend = "dividend"
|
|
CategoryCorporateAction = "corporate_action"
|
|
CategoryRegulation = "regulation"
|
|
CategoryRating = "rating"
|
|
CategoryExpansion = "expansion"
|
|
CategoryLeadership = "leadership"
|
|
CategoryMarket = "market"
|
|
CategoryGeneral = "general"
|
|
)
|
|
|
|
// Sentiment types
|
|
const (
|
|
SentimentPositive = "positive"
|
|
SentimentNegative = "negative"
|
|
SentimentNeutral = "neutral"
|
|
)
|
|
|
|
// Category keywords (Indonesian + English)
|
|
var categoryKeywords = map[string][]string{
|
|
CategoryEarnings: {
|
|
"laba", "rugi", "earnings", "profit", "net income", "pendapatan",
|
|
"revenue", "keuntungan", "kerugian", "loss", "income", "untung",
|
|
"quarterly", "kuartalan", "annual report", "laporan tahunan",
|
|
"eps", "earning per share",
|
|
},
|
|
CategoryDividend: {
|
|
"dividen", "dividend", "pembagian", "interim", "final dividend",
|
|
"cum date", "ex date", "payment date", "tanggal pembayaran",
|
|
"yield", "payout",
|
|
},
|
|
CategoryCorporateAction: {
|
|
"akuisisi", "merger", "acquisition", "rights issue", "stock split",
|
|
"reverse split", "buyback", "ipo", "penawaran umum", "private placement",
|
|
"tender offer", "spin off", "spinoff", "demerger", "konsolidasi",
|
|
"rights", "waran", "warrant", "obligasi", "bond", "sukuk",
|
|
},
|
|
CategoryRegulation: {
|
|
"ojk", "regulasi", "peraturan", "kebijakan", "regulation", "policy",
|
|
"compliance", "kepatuhan", "lisensi", "license", "izin", "permit",
|
|
"pemerintah", "government", "bapepam", "bei", "idx", "bursa",
|
|
},
|
|
CategoryRating: {
|
|
"rating", "peringkat", "upgrade", "downgrade", "outlook",
|
|
"stable", "positive", "negative", "credit rating", "moody",
|
|
"fitch", "s&p", "pefindo", "target price", "rekomendasi",
|
|
"buy", "sell", "hold", "analyst",
|
|
},
|
|
CategoryExpansion: {
|
|
"ekspansi", "expansion", "investasi", "investment", "proyek baru",
|
|
"new project", "pabrik", "factory", "plant", "cabang", "branch",
|
|
"pembangunan", "construction", "development", "joint venture", "jv",
|
|
"kerjasama", "partnership", "kontrak", "contract",
|
|
},
|
|
CategoryLeadership: {
|
|
"direktur", "director", "komisaris", "commissioner", "ceo", "cfo",
|
|
"president director", "management", "manajemen", "direksi",
|
|
"rups", "agm", "annual general meeting", "pengangkatan", "appointment",
|
|
"pengunduran", "resignation", "pergantian", "change",
|
|
},
|
|
CategoryMarket: {
|
|
"ihsg", "idx", "pasar modal", "bursa", "market", "saham",
|
|
"stock", "trading", "perdagangan", "volume", "kapitalisasi",
|
|
"market cap", "blue chip", "lq45", "idx80", "kompas100",
|
|
},
|
|
}
|
|
|
|
// Positive sentiment keywords
|
|
var positiveKeywords = []string{
|
|
// Indonesian
|
|
"naik", "untung", "tumbuh", "positif", "optimis", "meningkat",
|
|
"surplus", "berhasil", "sukses", "cemerlang", "bagus", "baik",
|
|
"membaik", "melonjak", "meroket", "tertinggi", "rekor",
|
|
"peningkatan", "pertumbuhan", "keuntungan", "laba bersih",
|
|
"ekspansi", "pemulihan", "recovery",
|
|
// English
|
|
"rise", "gain", "growth", "positive", "optimistic", "increase",
|
|
"surplus", "success", "excellent", "good", "improve", "surge",
|
|
"soar", "highest", "record", "profit", "expansion", "recovery",
|
|
"bullish", "upgrade", "beat", "exceed", "outperform",
|
|
}
|
|
|
|
// Negative sentiment keywords
|
|
var negativeKeywords = []string{
|
|
// Indonesian
|
|
"turun", "rugi", "anjlok", "negatif", "pesimis", "menurun",
|
|
"defisit", "gagal", "buruk", "memburuk", "jatuh", "tertekan",
|
|
"terendah", "penurunan", "kerugian", "merosot", "melemah",
|
|
"default", "bangkrut", "pailit", "koreksi", "tekanan",
|
|
// English
|
|
"fall", "loss", "plunge", "negative", "pessimistic", "decrease",
|
|
"deficit", "fail", "bad", "worsen", "drop", "pressure",
|
|
"lowest", "decline", "weak", "default", "bankrupt", "correction",
|
|
"bearish", "downgrade", "miss", "underperform", "concern", "risk",
|
|
}
|
|
|
|
// Critical news keywords (alerts)
|
|
var criticalKeywords = []string{
|
|
// Indonesian
|
|
"suspend", "suspensi", "fraud", "penipuan", "korupsi", "corruption",
|
|
"default", "gagal bayar", "bangkrut", "pailit", "bankruptcy",
|
|
"delisting", "pencabutan", "investigasi", "investigation",
|
|
"skandal", "scandal", "illegal", "ilegal", "pelanggaran", "violation",
|
|
"tuntutan", "lawsuit", "gugatan", "denda", "fine", "sanksi", "sanction",
|
|
"pkpu", "penundaan", "moratorium", "restrukturisasi utang",
|
|
// English
|
|
"suspend", "fraud", "corruption", "default", "bankrupt", "bankruptcy",
|
|
"delisting", "investigation", "scandal", "illegal", "violation",
|
|
"lawsuit", "fine", "sanction", "debt restructuring", "warning",
|
|
"material adverse", "going concern", "audit opinion", "disclaimer",
|
|
}
|
|
|
|
// categorizeNews determines the category of a news article
|
|
func categorizeNews(title, abstract string) string {
|
|
text := strings.ToLower(title + " " + abstract)
|
|
|
|
// Check each category
|
|
maxScore := 0
|
|
bestCategory := CategoryGeneral
|
|
|
|
for category, keywords := range categoryKeywords {
|
|
score := 0
|
|
for _, keyword := range keywords {
|
|
if strings.Contains(text, keyword) {
|
|
score++
|
|
}
|
|
}
|
|
if score > maxScore {
|
|
maxScore = score
|
|
bestCategory = category
|
|
}
|
|
}
|
|
|
|
return bestCategory
|
|
}
|
|
|
|
// scoreNewsSentiment analyzes sentiment of a news article
|
|
func scoreNewsSentiment(title, abstract string) (sentiment string, score float64) {
|
|
text := strings.ToLower(title + " " + abstract)
|
|
|
|
positiveScore := 0
|
|
negativeScore := 0
|
|
|
|
for _, keyword := range positiveKeywords {
|
|
if strings.Contains(text, keyword) {
|
|
positiveScore++
|
|
}
|
|
}
|
|
|
|
for _, keyword := range negativeKeywords {
|
|
if strings.Contains(text, keyword) {
|
|
negativeScore++
|
|
}
|
|
}
|
|
|
|
totalScore := positiveScore + negativeScore
|
|
if totalScore == 0 {
|
|
return SentimentNeutral, 0.0
|
|
}
|
|
|
|
// Calculate score from -1 (very negative) to +1 (very positive)
|
|
score = float64(positiveScore-negativeScore) / float64(totalScore)
|
|
|
|
if score > 0.2 {
|
|
sentiment = SentimentPositive
|
|
} else if score < -0.2 {
|
|
sentiment = SentimentNegative
|
|
} else {
|
|
sentiment = SentimentNeutral
|
|
}
|
|
|
|
return sentiment, score
|
|
}
|
|
|
|
// isNewsCritical checks if news contains critical/alert-worthy content
|
|
func isNewsCritical(title, abstract string) bool {
|
|
text := strings.ToLower(title + " " + abstract)
|
|
|
|
for _, keyword := range criticalKeywords {
|
|
if strings.Contains(text, keyword) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|