자동화 구축: Docker 이미지를 빌드, 푸시, 재시작하는 GitHub Actions 워크플로 추가 및 고도화된 매수 체결 로직 반영
Some checks failed
Build Push and Restart Compose / deploy (push) Failing after 12s

This commit is contained in:
hayato5246
2026-04-01 20:50:16 +09:00
parent d10b794c9f
commit 2aa433013b
4 changed files with 115 additions and 50 deletions

View File

@@ -39,7 +39,8 @@
"Bash(docker build:*)",
"Bash(docker compose:*)",
"Bash(tree:*)",
"Bash(go vet:*)"
"Bash(go vet:*)",
"Bash(python3:*)"
]
}
}

View File

@@ -0,0 +1,35 @@
name: Build Push and Restart Compose
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
env:
IMAGE_NAME: lshfly-registry.duckdns.org/stocksearch:latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login lshfly-registry.duckdns.org -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
- name: Build image
run: |
docker build -t ${IMAGE_NAME} .
- name: Push image
run: |
docker push ${IMAGE_NAME}
- name: Restart compose
run: |
cd /workspace/${{ gitea.repository }}
docker compose pull
docker compose up -d

0
README.md Normal file
View File

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"log"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
@@ -26,7 +27,7 @@ const (
cooldownMinutes = 5 // 동일 종목 재진입 쿨다운(분)
exitLoopSec = 5 // 청산 루프 주기(초)
entryLoopSec = 10 // 진입 루프 주기(초)
pendingCheckSec = 30 // pending 확인 주기(초)
pendingCheckSec = 5 // pending 확인 주기(초) — 시장가 주문 즉시 체결 대응
)
// AutoTradeService 자동매매 엔진 서비스
@@ -495,11 +496,16 @@ func (s *AutoTradeService) checkEntries() {
continue
}
// 포지션 등록
// 포지션 등록 (현재가 기준 예상 손절/익절가 미리 계산 → UI에 0원 방지)
estStop := int64(float64(sig.CurrentPrice) * (1 + rule.StopLossPct/100))
estProfit := int64(float64(sig.CurrentPrice) * (1 + rule.TakeProfitPct/100))
pos := &models.AutoTradePosition{
Code: code,
Name: sig.Name,
Qty: qty,
BuyPrice: sig.CurrentPrice,
StopLoss: estStop,
TakeProfit: estProfit,
OrderNo: result.OrderNo,
EntryTime: time.Now(),
RuleID: rule.ID,
@@ -580,6 +586,7 @@ func (s *AutoTradeService) checkExits() {
}
// checkPending pending 포지션 체결 확인
// 미체결 주문 조회(ka10075)에서 OrderNo가 사라지면 체결된 것으로 간주
func (s *AutoTradeService) checkPending() {
s.mu.RLock()
pending := make([]*models.AutoTradePosition, 0)
@@ -595,22 +602,32 @@ func (s *AutoTradeService) checkPending() {
return
}
balance, err := s.accountSvc.GetBalance()
// 미체결 주문 목록 조회 → 주문번호 집합 구성
unfilled, err := s.accountSvc.GetPendingOrders()
if err != nil {
log.Printf("[자동매매] 잔고 조회 실패: %v", err)
log.Printf("[자동매매] 미체결 조회 실패: %v", err)
return
}
unfilledMap := make(map[string]struct{}, len(unfilled))
for _, o := range unfilled {
unfilledMap[strings.TrimSpace(o.OrdNo)] = struct{}{}
}
for _, pos := range pending {
for _, stock := range balance.Stocks {
if stock.StkCd == pos.Code {
buyPrice, _ := strconv.ParseInt(stock.PurPric, 10, 64)
qty, _ := strconv.ParseInt(stock.RmndQty, 10, 64)
if qty <= 0 {
orderNo := strings.TrimSpace(pos.OrderNo)
if _, stillPending := unfilledMap[orderNo]; stillPending {
// 아직 미체결 목록에 있음 → 대기 유지
s.addLog("debug", pos.Code, fmt.Sprintf("체결 대기 중 (주문번호: %s)", orderNo))
continue
}
// 청산가 계산
// 미체결 목록에서 사라짐 → 체결(또는 취소) 완료
// 잔고에서 실제 매입단가 조회 시도
buyPrice := s.getBuyPriceFromBalance(pos.Code)
if buyPrice == 0 {
buyPrice = pos.BuyPrice // checkEntries에서 설정한 현재가 예상값
}
rule := s.findRule(pos.RuleID)
var stopLoss, takeProfit int64
if rule != nil && buyPrice > 0 {
@@ -621,23 +638,35 @@ func (s *AutoTradeService) checkPending() {
s.mu.Lock()
if p, ok := s.positions[pos.Code]; ok && p.Status == "pending" {
p.BuyPrice = buyPrice
p.Qty = qty
p.StopLoss = stopLoss
p.TakeProfit = takeProfit
p.Status = "open"
// ExitBeforeClose 규칙 값 저장
if rule != nil {
// 포지션 자체에 규칙 정보가 없으므로 로그만 기록
}
}
s.mu.Unlock()
s.addLog("info", pos.Code, fmt.Sprintf("매수 체결 확인: %s %d주 @ %d원 (손절: %d, 익절: %d)", pos.Name, qty, buyPrice, stopLoss, takeProfit))
break
}
s.addLog("info", pos.Code, fmt.Sprintf("매수 체결 확인: %s %d주 @ %d원 (손절: %d, 익절: %d)",
pos.Name, pos.Qty, buyPrice, stopLoss, takeProfit))
}
}
// getBuyPriceFromBalance 잔고 조회로 종목 매입단가 반환 (없으면 0)
func (s *AutoTradeService) getBuyPriceFromBalance(code string) int64 {
balance, err := s.accountSvc.GetBalance()
if err != nil {
return 0
}
for _, stock := range balance.Stocks {
stkCd := strings.TrimSpace(stock.StkCd)
// 키움 API 코드 형식 A-prefix 처리 (예: "A005930" → "005930")
if strings.HasPrefix(stkCd, "A") {
stkCd = stkCd[1:]
}
if stkCd == code {
price, _ := strconv.ParseInt(strings.TrimSpace(stock.PurPric), 10, 64)
return price
}
}
return 0
}
// executeSell 매도 주문 실행 및 포지션 업데이트