first commit
This commit is contained in:
152
services/theme_service.go
Normal file
152
services/theme_service.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"stocksearch/models"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ThemeService 테마 분석 서비스
|
||||
type ThemeService struct {
|
||||
kiwoom *KiwoomClient
|
||||
cache *CacheService
|
||||
}
|
||||
|
||||
var themeSvc *ThemeService
|
||||
|
||||
// GetThemeService 테마 서비스 싱글턴 반환
|
||||
func GetThemeService() *ThemeService {
|
||||
if themeSvc == nil {
|
||||
themeSvc = &ThemeService{
|
||||
kiwoom: GetKiwoomClient(),
|
||||
cache: GetCacheService(),
|
||||
}
|
||||
}
|
||||
return themeSvc
|
||||
}
|
||||
|
||||
// GetThemes ka90001: 테마그룹 목록 조회 (캐시 1분)
|
||||
// sortTp: "3"=상위등락률, "1"=상위기간수익률
|
||||
func (s *ThemeService) GetThemes(dateTp, sortTp string) ([]models.ThemeGroup, error) {
|
||||
cacheKey := fmt.Sprintf("themes:%s:%s", dateTp, sortTp)
|
||||
if cached, ok := s.cache.Get(cacheKey); ok {
|
||||
if groups, ok := cached.([]models.ThemeGroup); ok {
|
||||
return groups, nil
|
||||
}
|
||||
}
|
||||
|
||||
body, err := s.kiwoom.post("ka90001", "/api/dostk/thme", map[string]string{
|
||||
"qry_tp": "0", // 전체검색
|
||||
"stk_cd": "",
|
||||
"date_tp": dateTp,
|
||||
"thema_nm": "",
|
||||
"flu_pl_amt_tp": sortTp,
|
||||
"stex_tp": "1", // KRX
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("테마 목록 조회 실패: %w", err)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
ThemaGrp []struct {
|
||||
ThemaGrpCd string `json:"thema_grp_cd"`
|
||||
ThemaNm string `json:"thema_nm"`
|
||||
StkNum string `json:"stk_num"`
|
||||
FluSig string `json:"flu_sig"`
|
||||
FluRt string `json:"flu_rt"`
|
||||
RisingStkNum string `json:"rising_stk_num"`
|
||||
FallStkNum string `json:"fall_stk_num"`
|
||||
DtPrftRt string `json:"dt_prft_rt"`
|
||||
MainStk string `json:"main_stk"`
|
||||
} `json:"thema_grp"`
|
||||
ReturnCode int `json:"return_code"`
|
||||
ReturnMsg string `json:"return_msg"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("테마 목록 파싱 실패: %w", err)
|
||||
}
|
||||
if result.ReturnCode != 0 {
|
||||
return nil, fmt.Errorf("테마 목록 조회 실패: %s", result.ReturnMsg)
|
||||
}
|
||||
|
||||
groups := make([]models.ThemeGroup, 0, len(result.ThemaGrp))
|
||||
for _, g := range result.ThemaGrp {
|
||||
groups = append(groups, models.ThemeGroup{
|
||||
Code: g.ThemaGrpCd,
|
||||
Name: g.ThemaNm,
|
||||
StockCount: int(parseIntSafe(g.StkNum)),
|
||||
FluSig: g.FluSig,
|
||||
FluRt: parseFloatSafe(strings.TrimPrefix(g.FluRt, "+")),
|
||||
RisingCount: int(parseIntSafe(g.RisingStkNum)),
|
||||
FallCount: int(parseIntSafe(g.FallStkNum)),
|
||||
PeriodRt: parseFloatSafe(strings.TrimPrefix(g.DtPrftRt, "+")),
|
||||
MainStock: g.MainStk,
|
||||
})
|
||||
}
|
||||
|
||||
s.cache.Set(cacheKey, groups, time.Minute)
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
// GetThemeStocks ka90002: 테마구성종목 조회 (캐시 2분)
|
||||
func (s *ThemeService) GetThemeStocks(themeCode, dateTp string) (*models.ThemeDetail, error) {
|
||||
cacheKey := fmt.Sprintf("theme_stocks:%s:%s", themeCode, dateTp)
|
||||
if cached, ok := s.cache.Get(cacheKey); ok {
|
||||
if detail, ok := cached.(*models.ThemeDetail); ok {
|
||||
return detail, nil
|
||||
}
|
||||
}
|
||||
|
||||
body, err := s.kiwoom.post("ka90002", "/api/dostk/thme", map[string]string{
|
||||
"thema_grp_cd": themeCode,
|
||||
"date_tp": dateTp,
|
||||
"stex_tp": "1", // KRX
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("테마 구성종목 조회 실패: %w", err)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
FluRt string `json:"flu_rt"`
|
||||
DtPrftRt string `json:"dt_prft_rt"`
|
||||
ThemaCompStk []struct {
|
||||
StkCd string `json:"stk_cd"`
|
||||
StkNm string `json:"stk_nm"`
|
||||
CurPrc string `json:"cur_prc"`
|
||||
FluSig string `json:"flu_sig"`
|
||||
PredPre string `json:"pred_pre"`
|
||||
FluRt string `json:"flu_rt"`
|
||||
} `json:"thema_comp_stk"`
|
||||
ReturnCode int `json:"return_code"`
|
||||
ReturnMsg string `json:"return_msg"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("테마 구성종목 파싱 실패: %w", err)
|
||||
}
|
||||
if result.ReturnCode != 0 {
|
||||
return nil, fmt.Errorf("테마 구성종목 조회 실패: %s", result.ReturnMsg)
|
||||
}
|
||||
|
||||
stocks := make([]models.ThemeStock, 0, len(result.ThemaCompStk))
|
||||
for _, s := range result.ThemaCompStk {
|
||||
stocks = append(stocks, models.ThemeStock{
|
||||
Code: s.StkCd,
|
||||
Name: s.StkNm,
|
||||
CurPrc: absParseIntSafe(s.CurPrc),
|
||||
FluSig: s.FluSig,
|
||||
PredPre: parseIntSafe(s.PredPre),
|
||||
FluRt: parseFloatSafe(strings.TrimPrefix(s.FluRt, "+")),
|
||||
})
|
||||
}
|
||||
|
||||
detail := &models.ThemeDetail{
|
||||
FluRt: parseFloatSafe(strings.TrimPrefix(result.FluRt, "+")),
|
||||
PeriodRt: parseFloatSafe(strings.TrimPrefix(result.DtPrftRt, "+")),
|
||||
Stocks: stocks,
|
||||
}
|
||||
|
||||
s.cache.Set(cacheKey, detail, 2*time.Minute)
|
||||
return detail, nil
|
||||
}
|
||||
Reference in New Issue
Block a user