121 lines
5.2 KiB
Java
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();
|
|
}
|
|
}
|