// Package service 定义业务逻辑层,位于 MVC 架构中 Controller 与 Model 之间。 // 本层封装领域业务(行情、策略、回测),不感知 HTTP 与具体数据源实现。 package service import ( "context" "fmt" "time" "stock-analysis/internal/datasource" "stock-analysis/internal/model" ) // MarketService 提供行情查询业务。 type MarketService struct { ds datasource.MarketDataSource } // NewMarketService 创建行情服务。 func NewMarketService(ds datasource.MarketDataSource) *MarketService { return &MarketService{ds: ds} } // ListConvertibles 返回全量可转债实时行情(过滤已退市转债)。 func (s *MarketService) ListConvertibles(ctx context.Context) ([]model.ConvertibleBond, error) { all, err := s.ds.FetchConvertibles(ctx) if err != nil { return nil, err } // 过滤掉已退市的转债:退市后东财接口返回的价格为 0。 var active []model.ConvertibleBond for _, b := range all { if b.Price > 0 { active = append(active, b) } } return active, nil } // GetDailyKline 返回指定可转债的日K线(自 beg 至今)。 func (s *MarketService) GetDailyKline(ctx context.Context, code, beg string) ([]model.Kline, error) { // klt=101 为日K,beg/end 格式 20060102;end 取极大值表示拉取至今。 return s.ds.FetchKline(ctx, code, "101", beg, "20500101") } // FetchAllDailyKline 按年度窗口分批拉取指定标的的全量日K线。 // fromYear 为起始年份(如烽火转债 2020),到当前年份为止,保证单次请求数据量可控。 func (s *MarketService) FetchAllDailyKline(ctx context.Context, code string, fromYear int) ([]model.Kline, error) { curYear := time.Now().Year() var all []model.Kline for year := fromYear; year <= curYear; year++ { beg := fmt.Sprintf("%04d0101", year) end := fmt.Sprintf("%04d1231", year) ks, err := s.ds.FetchKline(ctx, code, "101", beg, end) if err != nil { return nil, fmt.Errorf("fetch kline %d: %w", year, err) } all = append(all, ks...) } return all, nil } // FetchFullKline 单次请求拉取指定标的自 fromYear 起的全量日K线。 // 适合批量入库场景:每只转债仅一次请求,大幅降低全市场抓取时的请求总量。 // 可转债存续期最长约 6 年(约 1500 根),单次请求即可返回完整历史。 func (s *MarketService) FetchFullKline(ctx context.Context, code string, fromYear int) ([]model.Kline, error) { beg := fmt.Sprintf("%04d0101", fromYear) return s.ds.FetchKline(ctx, code, "101", beg, "20500101") } // FetchIncrementalKline 增量拉取日K线:从 begDate 起到当前日期。 // begDate 格式为 YYYYMMDD(如 "20260801"),对应数据源 API 入参;end 取极大值表示拉取至今。 // 用于增量入库:配合 LastKlineDate 使用,避免重复拉取已存档数据。 func (s *MarketService) FetchIncrementalKline(ctx context.Context, code, begDate string) ([]model.Kline, error) { return s.ds.FetchKline(ctx, code, "101", begDate, "20500101") }