up
This commit is contained in:
+100
-6
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user