This commit is contained in:
2026-04-20 22:51:41 +02:00
parent 89fc0119f3
commit 81eec53978
27 changed files with 1067 additions and 399 deletions
+100 -6
View File
@@ -47,14 +47,25 @@ type tickerEntry struct {
type submissionsResponse struct {
Filings struct {
Recent struct {
Form []string `json:"form"`
AccessionNumber []string `json:"accessionNumber"`
FilingDate []string `json:"filingDate"`
PrimaryDocument []string `json:"primaryDocument"`
Form []string `json:"form"`
AccessionNumber []string `json:"accessionNumber"`
FilingDate []string `json:"filingDate"`
PrimaryDocument []string `json:"primaryDocument"`
Items []string `json:"items"`
} `json:"recent"`
} `json:"filings"`
}
// CompanyEvent représente un événement 8-K significatif (ex: changement de direction).
type CompanyEvent struct {
Ticker string
EventType string // "ceo_change"
Title string
AccessionNo string
FilingDate string
FilingURL string
}
type form4Doc struct {
Issuer struct {
Symbol string `xml:"issuerTradingSymbol"`
@@ -106,7 +117,24 @@ func New() *Client {
// RecentInsiderBuys retourne les achats d'initiés (code P) pour un ticker
// sur les 30 derniers jours.
// isUSListed retourne false pour les tickers européens avec suffixe de bourse (.L, .PA, .DE…)
func isUSListed(ticker string) bool {
if idx := strings.LastIndex(ticker, "."); idx > 0 {
suffix := ticker[idx+1:]
// Suffixes US valides : A, B (BRK.A, BRK.B) → longueur 1 mais lettre unique
// Suffixes européens : L, PA, DE, AS, BR, HE, OL, ST, CO, MC, MI, VI…
if len(suffix) >= 2 {
return false // .PA, .DE, .AS etc. → non-US
}
}
return true
}
func (c *Client) RecentInsiderBuys(ticker string) ([]InsiderTrade, error) {
if !isUSListed(ticker) {
return nil, nil // silencieux pour les titres non-US
}
cik, err := c.lookupCIK(ticker)
if err != nil {
return nil, fmt.Errorf("CIK not found for %s: %w", ticker, err)
@@ -140,6 +168,72 @@ func (c *Client) RecentInsiderBuys(ticker string) ([]InsiderTrade, error) {
return trades, nil
}
// Recent8KEvents retourne les 8-K Item 5.02 (changements de direction) des 30 derniers jours.
func (c *Client) Recent8KEvents(ticker string) ([]CompanyEvent, error) {
if !isUSListed(ticker) {
return nil, nil
}
cik, err := c.lookupCIK(ticker)
if err != nil {
return nil, nil // ticker inconnu → silencieux
}
url := fmt.Sprintf("%s/submissions/CIK%s.json", baseURL, cik)
resp, err := c.get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var sub submissionsResponse
if err := json.NewDecoder(resp.Body).Decode(&sub); err != nil {
return nil, err
}
cutoff := time.Now().AddDate(0, 0, -30).Format("2006-01-02")
forms := sub.Filings.Recent.Form
accs := sub.Filings.Recent.AccessionNumber
dates := sub.Filings.Recent.FilingDate
items := sub.Filings.Recent.Items
var events []CompanyEvent
for i, form := range forms {
if form != "8-K" && form != "8-K/A" {
continue
}
date := ""
if i < len(dates) {
date = dates[i]
}
if date != "" && date < cutoff {
break // filings triés du plus récent au plus ancien
}
itemStr := ""
if i < len(items) {
itemStr = items[i]
}
if !strings.Contains(itemStr, "5.02") {
continue
}
acc := ""
if i < len(accs) {
acc = accs[i]
}
accNoDashes := strings.ReplaceAll(acc, "-", "")
filingURL := fmt.Sprintf("https://www.sec.gov/Archives/edgar/data/%s/%s/", cik, accNoDashes)
events = append(events, CompanyEvent{
Ticker: ticker,
EventType: "ceo_change",
Title: fmt.Sprintf("Executive change (8-K §5.02) — %s", ticker),
AccessionNo: acc,
FilingDate: date,
FilingURL: filingURL,
})
}
return events, nil
}
// ---- méthodes internes ----
func (c *Client) lookupCIK(ticker string) (string, error) {
@@ -254,8 +348,8 @@ func (c *Client) parseForm4(cik, accessionNo, primaryDoc, ticker string) ([]Insi
var trades []InsiderTrade
for _, tx := range doc.NonDerivativeTable.Transactions {
code := tx.Coding.Code
// On garde achats (P) et attributions significatives (A avec prix > 0)
if code != "P" && !(code == "A" && tx.Amounts.Price.Value > 0) {
// P = achat, S = vente, A = attribution avec prix > 0
if code != "P" && code != "S" && !(code == "A" && tx.Amounts.Price.Value > 0) {
continue
}
shares := tx.Amounts.Shares.Value