자산 현황 및 자동매매 페이지 제거:
Some checks failed
Build Push and Restart Compose / deploy (push) Failing after 11m20s

- `/templates/pages/asset.html`, `/templates/pages/autotrade.html` HTML 템플릿 삭제.
- `/static/js/asset.js`, `/static/js/autotrade.js` 클라이언트 스크립트 제거.
- 관련 함수 및 초기화 로직 삭제 (자산 조회 및 자동매매 기능 비활성화).
This commit is contained in:
hayato5246
2026-04-07 21:43:24 +09:00
parent 5a29d50752
commit 5aeb5f2b80
47 changed files with 279 additions and 6361 deletions

View File

@@ -2,7 +2,6 @@ package services
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
@@ -14,8 +13,6 @@ import (
"strings"
"sync"
"time"
"golang.org/x/time/rate"
)
// cntrStrCacheEntry getCntrStr 캐시 항목
@@ -24,14 +21,30 @@ type cntrStrCacheEntry struct {
expiresAt time.Time
}
// apiJob 큐에 넣을 API 요청 작업
type apiJob struct {
req *http.Request
result chan apiResult
}
// apiResult 워커의 응답
type apiResult struct {
resp *http.Response
err error
}
// KiwoomClient 키움증권 REST API HTTP 클라이언트
// 모든 API 호출은 단일 워커 큐를 통해 순차 처리 (429 방지)
// 1초 윈도우 내 처리 시간 합산 → 남은 시간 대기 후 다음 배치
type KiwoomClient struct {
httpClient *http.Client
tokenService *TokenService
limiter *rate.Limiter
cntrStrCache sync.Map // stockCode → cntrStrCacheEntry (5초 TTL)
queue chan apiJob // API 요청 큐
cntrStrCache sync.Map // stockCode → cntrStrCacheEntry (5초 TTL)
}
const apiQueueSize = 256 // 큐 버퍼 크기
var kiwoomClient *KiwoomClient
// GetKiwoomClient 키움 클라이언트 싱글턴 반환
@@ -40,14 +53,39 @@ func GetKiwoomClient() *KiwoomClient {
kiwoomClient = &KiwoomClient{
httpClient: &http.Client{Timeout: 10 * time.Second},
tokenService: GetTokenService(),
// 초당 1건, 버스트 1 → 완전 직렬화 (키움 API 실질 한도 ~1req/s per API ID)
limiter: rate.NewLimiter(rate.Limit(1), 1),
queue: make(chan apiJob, apiQueueSize),
}
go kiwoomClient.worker()
}
return kiwoomClient
}
// post 공통 POST 요청 (api-id 헤더, JSON body, Rate Limit 적용, 429 재시도)
// worker 단일 고루틴이 큐에서 작업을 꺼내 순차 실행
// 1건 실행 후 (1초 - 처리시간)만큼 대기 → 다음 1건
func (k *KiwoomClient) worker() {
for job := range k.queue {
start := time.Now()
resp, err := k.httpClient.Do(job.req)
elapsed := time.Since(start)
job.result <- apiResult{resp: resp, err: err}
// 1초 - 처리시간 = 대기시간 (처리가 1초 이상이면 대기 없이 즉시 다음)
if wait := time.Second - elapsed; wait > 0 {
time.Sleep(wait)
}
}
}
// enqueue HTTP 요청을 큐에 넣고 응답 대기
func (k *KiwoomClient) enqueue(req *http.Request) (*http.Response, error) {
ch := make(chan apiResult, 1)
k.queue <- apiJob{req: req, result: ch}
res := <-ch
return res.resp, res.err
}
// post 공통 POST 요청 (api-id 헤더, JSON body, 큐 기반 순차 처리, 429 재시도)
func (k *KiwoomClient) post(apiID string, path string, body map[string]string) ([]byte, error) {
const maxRetries = 3
backoff := 1 * time.Second
@@ -55,10 +93,6 @@ func (k *KiwoomClient) post(apiID string, path string, body map[string]string) (
data, _ := json.Marshal(body)
for attempt := 0; attempt < maxRetries; attempt++ {
if err := k.limiter.Wait(context.Background()); err != nil {
return nil, fmt.Errorf("Rate Limit 대기 실패: %w", err)
}
req, err := http.NewRequest("POST", config.App.BaseURL+path, bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("요청 생성 실패: %w", err)
@@ -70,7 +104,7 @@ func (k *KiwoomClient) post(apiID string, path string, body map[string]string) (
req.Header.Set("cont-yn", "N")
req.Header.Set("next-key", "")
resp, err := k.httpClient.Do(req)
resp, err := k.enqueue(req)
if err != nil {
return nil, fmt.Errorf("API 요청 실패: %w", err)
}
@@ -109,10 +143,6 @@ func (k *KiwoomClient) post(apiID string, path string, body map[string]string) (
// postPaged 연속조회 지원 POST 요청 - 응답 헤더(cont-yn, next-key) 함께 반환
func (k *KiwoomClient) postPaged(apiID, path string, body map[string]string, contYn, nextKey string) ([]byte, string, string, error) {
if err := k.limiter.Wait(context.Background()); err != nil {
return nil, "", "", fmt.Errorf("Rate Limit 대기 실패: %w", err)
}
data, _ := json.Marshal(body)
req, err := http.NewRequest("POST", config.App.BaseURL+path, bytes.NewReader(data))
if err != nil {
@@ -125,13 +155,13 @@ func (k *KiwoomClient) postPaged(apiID, path string, body map[string]string, con
req.Header.Set("cont-yn", contYn)
req.Header.Set("next-key", nextKey)
resp, err := k.httpClient.Do(req)
resp, err := k.enqueue(req)
if err != nil {
return nil, "", "", fmt.Errorf("API 요청 실패: %w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, "", "", fmt.Errorf("응답 읽기 실패: %w", err)
}
@@ -171,14 +201,14 @@ func (k *KiwoomClient) fetchPrice(stkCd string) (*models.StockPrice, error) {
}
var result struct {
StkNm string `json:"stk_nm"`
CurPrc string `json:"cur_prc"`
PredPre string `json:"pred_pre"`
FluRt string `json:"flu_rt"`
TrdeQty string `json:"trde_qty"`
OpenPric string `json:"open_pric"`
HighPric string `json:"high_pric"`
LowPric string `json:"low_pric"`
StkNm string `json:"stk_nm"`
CurPrc string `json:"cur_prc"`
PredPre string `json:"pred_pre"`
FluRt string `json:"flu_rt"`
TrdeQty string `json:"trde_qty"`
OpenPric string `json:"open_pric"`
HighPric string `json:"high_pric"`
LowPric string `json:"low_pric"`
ReturnCode int `json:"return_code"`
ReturnMsg string `json:"return_msg"`
}
@@ -259,12 +289,12 @@ func (k *KiwoomClient) GetDailyChart(stockCode string) ([]models.CandleData, err
var result struct {
StkDdwkmm []struct {
Date string `json:"date"`
OpenPric string `json:"open_pric"`
HighPric string `json:"high_pric"`
LowPric string `json:"low_pric"`
Date string `json:"date"`
OpenPric string `json:"open_pric"`
HighPric string `json:"high_pric"`
LowPric string `json:"low_pric"`
ClosePric string `json:"close_pric"`
TrdeQty string `json:"trde_qty"`
TrdeQty string `json:"trde_qty"`
} `json:"stk_ddwkmm"`
ReturnCode int `json:"return_code"`
ReturnMsg string `json:"return_msg"`
@@ -378,15 +408,15 @@ func (k *KiwoomClient) GetTopVolumeStocks(market string, count int) ([]models.St
}
body, err := k.post("ka10030", "/api/dostk/rkinfo", map[string]string{
"mrkt_tp": mrktTp,
"sort_tp": "1", // 거래량 기준 정렬
"mrkt_tp": mrktTp,
"sort_tp": "1", // 거래량 기준 정렬
"mang_stk_incls": "1", // 관리종목 미포함
"crd_tp": "0",
"trde_qty_tp": "0",
"pric_tp": "0",
"trde_prica_tp": "0",
"mrkt_open_tp": "0",
"stex_tp": "3", // 통합
"crd_tp": "0",
"trde_qty_tp": "0",
"pric_tp": "0",
"trde_prica_tp": "0",
"mrkt_open_tp": "0",
"stex_tp": "3", // 통합
})
if err != nil {
return nil, err
@@ -485,15 +515,15 @@ func (k *KiwoomClient) GetTopFluctuation(market string, ascending bool, count in
}
body, err := k.post("ka10027", "/api/dostk/rkinfo", map[string]string{
"mrkt_tp": mrktTp,
"sort_tp": sortTp,
"trde_qty_cnd": "0000", // 거래량 전체
"stk_cnd": "0", // 종목조건 전체
"crd_cnd": "0", // 신용조건 전체
"updown_incls": "1", // 상하한포함
"pric_cnd": "0", // 가격조건 전체
"trde_prica_cnd": "0", // 거래대금조건 전체
"stex_tp": "1", // KRX
"mrkt_tp": mrktTp,
"sort_tp": sortTp,
"trde_qty_cnd": "0000", // 거래량 전체
"stk_cnd": "0", // 종목조건 전체
"crd_cnd": "0", // 신용조건 전체
"updown_incls": "1", // 상하한포함
"pric_cnd": "0", // 가격조건 전체
"trde_prica_cnd": "0", // 거래대금조건 전체
"stex_tp": "1", // KRX
})
if err != nil {
return nil, err