Files
goldenChart/backend/src/main/java/com/goldenchart/service/PaperSnapshotService.java
T
2026-05-31 03:47:07 +09:00

121 lines
5.2 KiB
Java

package com.goldenchart.service;
import com.goldenchart.dto.PaperDailySnapshotDto;
import com.goldenchart.entity.GcPaperAccount;
import com.goldenchart.entity.GcPaperDailySnapshot;
import com.goldenchart.entity.GcPaperPosition;
import com.goldenchart.repository.GcPaperAccountRepository;
import com.goldenchart.repository.GcPaperDailySnapshotRepository;
import com.goldenchart.repository.GcPaperPositionRepository;
import com.goldenchart.storage.Ta4jStorage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.ta4j.core.Bar;
import org.ta4j.core.BarSeries;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class PaperSnapshotService {
private static final ZoneId KST = ZoneId.of("Asia/Seoul");
private final GcPaperDailySnapshotRepository snapshotRepo;
private final GcPaperAccountRepository accountRepo;
private final GcPaperPositionRepository positionRepo;
private final Ta4jStorage ta4jStorage;
@Transactional(readOnly = true)
public List<PaperDailySnapshotDto> list(Long accountId, LocalDate from, LocalDate to) {
LocalDate f = from != null ? from : LocalDate.now(KST).minusDays(90);
LocalDate t = to != null ? to : LocalDate.now(KST);
return snapshotRepo.findByAccountIdAndSnapshotDateBetweenOrderBySnapshotDateAsc(accountId, f, t)
.stream().map(this::toDto).toList();
}
public void captureEodForAllAccounts() {
LocalDate today = LocalDate.now(KST);
for (GcPaperAccount account : accountRepo.findAll()) {
try {
captureEod(account, today);
} catch (Exception e) {
log.warn("[PaperSnapshot] account {} failed: {}", account.getId(), e.getMessage());
}
}
}
public void captureEod(GcPaperAccount account, LocalDate date) {
List<GcPaperPosition> positions = positionRepo.findByAccountIdOrderBySymbolAsc(account.getId());
Map<String, Double> marks = new HashMap<>();
double stockEval = 0;
for (GcPaperPosition p : positions) {
double qty = p.getQuantity().doubleValue();
if (qty <= 0) continue;
double mark = resolveMark(p.getSymbol(), p.getAvgPrice().doubleValue());
marks.put(p.getSymbol(), mark);
stockEval += mark * qty;
}
double cash = account.getCashBalance().doubleValue();
double total = cash + stockEval;
double initial = account.getInitialCapitalSnapshot() != null
? account.getInitialCapitalSnapshot().doubleValue() : cash;
double cumRet = initial > 0 ? (total - initial) / initial * 100 : 0;
GcPaperDailySnapshot prev = snapshotRepo
.findByAccountIdAndSnapshotDate(account.getId(), date.minusDays(1))
.orElse(null);
double prevTotal = prev != null ? prev.getTotalAsset().doubleValue() : initial;
double dailyPnl = total - prevTotal;
double dailyRet = prevTotal > 0 ? dailyPnl / prevTotal * 100 : 0;
GcPaperDailySnapshot snap = snapshotRepo.findByAccountIdAndSnapshotDate(account.getId(), date)
.orElse(GcPaperDailySnapshot.builder()
.accountId(account.getId())
.snapshotDate(date)
.build());
snap.setCashBalance(BigDecimal.valueOf(cash).setScale(2, RoundingMode.HALF_UP));
snap.setStockEvalAmount(BigDecimal.valueOf(stockEval).setScale(2, RoundingMode.HALF_UP));
snap.setTotalAsset(BigDecimal.valueOf(total).setScale(2, RoundingMode.HALF_UP));
snap.setDailyPnl(BigDecimal.valueOf(dailyPnl).setScale(2, RoundingMode.HALF_UP));
snap.setDailyReturnPct(BigDecimal.valueOf(dailyRet).setScale(4, RoundingMode.HALF_UP));
snap.setCumulativeReturnPct(BigDecimal.valueOf(cumRet).setScale(4, RoundingMode.HALF_UP));
snapshotRepo.save(snap);
}
private double resolveMark(String symbol, double fallback) {
try {
if (!ta4jStorage.exists(symbol, "1m")) return fallback;
BarSeries series = ta4jStorage.getOrCreate(symbol, "1m");
if (!series.isEmpty()) {
Bar last = series.getLastBar();
return last.getClosePrice().doubleValue();
}
} catch (Exception ignored) {
}
return fallback;
}
private PaperDailySnapshotDto toDto(GcPaperDailySnapshot s) {
return PaperDailySnapshotDto.builder()
.snapshotDate(s.getSnapshotDate().toString())
.cashBalance(s.getCashBalance().doubleValue())
.stockEvalAmount(s.getStockEvalAmount().doubleValue())
.totalAsset(s.getTotalAsset().doubleValue())
.dailyPnl(s.getDailyPnl().doubleValue())
.dailyReturnPct(s.getDailyReturnPct().doubleValue())
.cumulativeReturnPct(s.getCumulativeReturnPct().doubleValue())
.build();
}
}