모의투자 로직 변경
This commit is contained in:
@@ -5,6 +5,7 @@ import com.goldenchart.dto.*;
|
||||
import com.goldenchart.entity.*;
|
||||
import com.goldenchart.repository.*;
|
||||
import com.goldenchart.storage.Ta4jStorage;
|
||||
import com.goldenchart.trading.TradingAccess;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -51,9 +52,10 @@ public class PaperTradingService {
|
||||
// ── 조회 ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public PaperSummaryDto getSummary(Long userId, String deviceId, Map<String, Double> markPrices) {
|
||||
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId, app);
|
||||
public PaperSummaryDto getSummary(Long userId, Map<String, Double> markPrices) {
|
||||
long uid = TradingAccess.requireUserId(userId);
|
||||
GcAppSettings app = appSettingsService.getEntity(uid, null);
|
||||
GcPaperAccount account = getOrCreateAccount(uid, app);
|
||||
|
||||
List<GcPaperPosition> positions = positionRepo.findByAccountIdOrderBySymbolAsc(account.getId());
|
||||
double stockEval = 0;
|
||||
@@ -121,38 +123,40 @@ public class PaperTradingService {
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<PaperAllocationDto> listAllocations(Long userId, String deviceId,
|
||||
Map<String, Double> markPrices) {
|
||||
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId, app);
|
||||
PaperSummaryDto s = getSummary(userId, deviceId, markPrices);
|
||||
public List<PaperAllocationDto> listAllocations(Long userId, Map<String, Double> markPrices) {
|
||||
long uid = TradingAccess.requireUserId(userId);
|
||||
GcAppSettings app = appSettingsService.getEntity(uid, null);
|
||||
GcPaperAccount account = getOrCreateAccount(uid, app);
|
||||
PaperSummaryDto s = getSummary(uid, markPrices);
|
||||
return allocationService.listWithMetrics(account.getId(), markPrices, s.getTotalAsset());
|
||||
}
|
||||
|
||||
public List<PaperAllocationDto> bulkAllocations(Long userId, String deviceId,
|
||||
PaperAllocationBulkRequest req) {
|
||||
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId, app);
|
||||
public List<PaperAllocationDto> bulkAllocations(Long userId, PaperAllocationBulkRequest req) {
|
||||
long uid = TradingAccess.requireUserId(userId);
|
||||
GcAppSettings app = appSettingsService.getEntity(uid, null);
|
||||
GcPaperAccount account = getOrCreateAccount(uid, app);
|
||||
BigDecimal defaultMax = account.getInitialCapitalSnapshot() != null
|
||||
? account.getInitialCapitalSnapshot()
|
||||
: app.getPaperInitialCapital();
|
||||
return allocationService.bulkUpsert(account.getId(), req, defaultMax);
|
||||
}
|
||||
|
||||
public PaperAllocationDto patchAllocation(Long userId, String deviceId, String symbol,
|
||||
public PaperAllocationDto patchAllocation(Long userId, String symbol,
|
||||
PaperAllocationPatchRequest req) {
|
||||
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId, app);
|
||||
long uid = TradingAccess.requireUserId(userId);
|
||||
GcAppSettings app = appSettingsService.getEntity(uid, null);
|
||||
GcPaperAccount account = getOrCreateAccount(uid, app);
|
||||
return allocationService.patch(account.getId(), symbol, req);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public PaperPageDto<PaperTradeDto> listTrades(Long userId, String deviceId,
|
||||
public PaperPageDto<PaperTradeDto> listTrades(Long userId,
|
||||
String symbol, String side, String source,
|
||||
LocalDateTime from, LocalDateTime to,
|
||||
int page, int size) {
|
||||
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId, app);
|
||||
long uid = TradingAccess.requireUserId(userId);
|
||||
GcAppSettings app = appSettingsService.getEntity(uid, null);
|
||||
GcPaperAccount account = getOrCreateAccount(uid, app);
|
||||
PageRequest pr = PageRequest.of(Math.max(0, page), Math.min(100, Math.max(1, size)));
|
||||
Page<GcPaperTrade> result = tradeRepo.findFiltered(
|
||||
account.getId(), symbol, side, source, from, to, pr);
|
||||
@@ -165,39 +169,43 @@ public class PaperTradingService {
|
||||
.build();
|
||||
}
|
||||
|
||||
public List<PaperTradeDto> listTradesLegacy(Long userId, String deviceId) {
|
||||
return listTrades(userId, deviceId, null, null, null, null, null, 0, 100).getContent();
|
||||
public List<PaperTradeDto> listTradesLegacy(Long userId) {
|
||||
return listTrades(userId, null, null, null, null, null, 0, 100).getContent();
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public PaperPageDto<PaperLedgerEntryDto> listLedger(Long userId, String deviceId,
|
||||
public PaperPageDto<PaperLedgerEntryDto> listLedger(Long userId,
|
||||
LocalDateTime from, LocalDateTime to,
|
||||
int page, int size) {
|
||||
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId, app);
|
||||
long uid = TradingAccess.requireUserId(userId);
|
||||
GcAppSettings app = appSettingsService.getEntity(uid, null);
|
||||
GcPaperAccount account = getOrCreateAccount(uid, app);
|
||||
return ledgerService.list(account.getId(), from, to, page, size);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<PaperDailySnapshotDto> listDailySnapshots(Long userId, String deviceId,
|
||||
public List<PaperDailySnapshotDto> listDailySnapshots(Long userId,
|
||||
LocalDate from, LocalDate to) {
|
||||
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId, app);
|
||||
long uid = TradingAccess.requireUserId(userId);
|
||||
GcAppSettings app = appSettingsService.getEntity(uid, null);
|
||||
GcPaperAccount account = getOrCreateAccount(uid, app);
|
||||
return snapshotService.list(account.getId(), from, to);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<PaperOrderDto> listPendingOrders(Long userId, String deviceId) {
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId,
|
||||
appSettingsService.getEntity(userId, deviceId));
|
||||
public List<PaperOrderDto> listPendingOrders(Long userId) {
|
||||
long uid = TradingAccess.requireUserId(userId);
|
||||
GcPaperAccount account = getOrCreateAccount(uid,
|
||||
appSettingsService.getEntity(uid, null));
|
||||
return orderRepo.findByAccountIdAndStatusOrderByCreatedAtDesc(account.getId(), "PENDING")
|
||||
.stream().map(this::toOrderDto).toList();
|
||||
}
|
||||
|
||||
// ── 주문 ─────────────────────────────────────────────────────────────────
|
||||
|
||||
public PaperPlaceOrderResult placeOrder(Long userId, String deviceId, PaperOrderRequest req) {
|
||||
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
|
||||
public PaperPlaceOrderResult placeOrder(Long userId, PaperOrderRequest req) {
|
||||
long uid = TradingAccess.requireUserId(userId);
|
||||
GcAppSettings app = appSettingsService.getEntity(uid, null);
|
||||
if (!Boolean.TRUE.equals(app.getPaperTradingEnabled())) {
|
||||
throw new IllegalStateException("모의투자가 비활성화되어 있습니다.");
|
||||
}
|
||||
@@ -217,7 +225,7 @@ public class PaperTradingService {
|
||||
String source = req.getSource() != null ? req.getSource() : "MANUAL";
|
||||
String orderKind = req.getOrderKind() != null ? req.getOrderKind() : "limit";
|
||||
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId, app);
|
||||
GcPaperAccount account = getOrCreateAccount(uid, app);
|
||||
double qty = req.getQuantity() != null ? req.getQuantity() : 0;
|
||||
if (qty <= 0) {
|
||||
qty = resolveAutoQuantity(account, app, req.getMarket(), side, price,
|
||||
@@ -233,14 +241,15 @@ public class PaperTradingService {
|
||||
return PaperPlaceOrderResult.builder().order(toOrderDto(order)).build();
|
||||
}
|
||||
|
||||
PaperTradeDto trade = executeTrade(userId, deviceId, app, req.getMarket(), side, orderKind, source,
|
||||
PaperTradeDto trade = executeTrade(uid, app, req.getMarket(), side, orderKind, source,
|
||||
req.getStrategyId(), price, qty, null, false);
|
||||
return PaperPlaceOrderResult.builder().trade(trade).build();
|
||||
}
|
||||
|
||||
public PaperOrderDto cancelOrder(Long userId, String deviceId, Long orderId) {
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId,
|
||||
appSettingsService.getEntity(userId, deviceId));
|
||||
public PaperOrderDto cancelOrder(Long userId, Long orderId) {
|
||||
long uid = TradingAccess.requireUserId(userId);
|
||||
GcPaperAccount account = getOrCreateAccount(uid,
|
||||
appSettingsService.getEntity(uid, null));
|
||||
GcPaperOrder order = orderRepo.findById(orderId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("주문을 찾을 수 없습니다."));
|
||||
if (!order.getAccountId().equals(account.getId())) {
|
||||
@@ -255,12 +264,17 @@ public class PaperTradingService {
|
||||
return toOrderDto(order);
|
||||
}
|
||||
|
||||
public void tryExecuteOnSignal(String deviceId, Long userId, String market,
|
||||
public void tryExecuteOnSignal(Long userId, String market,
|
||||
Long strategyId, String signalType, double price) {
|
||||
if (deviceId == null || deviceId.isBlank()) return;
|
||||
long uid;
|
||||
try {
|
||||
uid = TradingAccess.requireUserId(userId);
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
if (!"BUY".equals(signalType) && !"SELL".equals(signalType)) return;
|
||||
|
||||
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
|
||||
GcAppSettings app = appSettingsService.getEntity(uid, null);
|
||||
if (!Boolean.TRUE.equals(app.getPaperTradingEnabled())) return;
|
||||
if (!Boolean.TRUE.equals(app.getPaperAutoTradeEnabled())) return;
|
||||
List<GcLiveStrategySettings> liveForMarket = liveSettingsRepo.findActiveByMarket(market).stream()
|
||||
@@ -270,7 +284,7 @@ public class PaperTradingService {
|
||||
if (!Boolean.TRUE.equals(app.getLiveStrategyCheck()) && !marketActive) return;
|
||||
|
||||
try {
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId, app);
|
||||
GcPaperAccount account = getOrCreateAccount(uid, app);
|
||||
String positionMode = liveForMarket.stream()
|
||||
.map(GcLiveStrategySettings::getPositionMode)
|
||||
.filter(m -> m != null && !m.isBlank())
|
||||
@@ -299,7 +313,7 @@ public class PaperTradingService {
|
||||
if (denom <= 0 || budget < minOrder) return;
|
||||
double qty = budget / denom;
|
||||
if (qty * execPrice < minOrder) return;
|
||||
executeTrade(userId, deviceId, app, market, "BUY", "market", "STRATEGY",
|
||||
executeTrade(uid, app, market, "BUY", "market", "STRATEGY",
|
||||
strategyId, price, qty, null, true);
|
||||
log.info("[Paper] STRATEGY BUY {} qty≈{} @ {}", market, qty, price);
|
||||
} else {
|
||||
@@ -308,7 +322,7 @@ public class PaperTradingService {
|
||||
if (pos == null || pos.getQuantity().doubleValue() <= 0) return;
|
||||
double avail = availableSellQty(account.getId(), market, pos.getQuantity().doubleValue());
|
||||
if (avail <= 0) return;
|
||||
executeTrade(userId, deviceId, app, market, "SELL", "market", "STRATEGY",
|
||||
executeTrade(uid, app, market, "SELL", "market", "STRATEGY",
|
||||
strategyId, price, avail, null, true);
|
||||
log.info("[Paper] STRATEGY SELL {} qty={} @ {}", market, avail, price);
|
||||
}
|
||||
@@ -332,11 +346,13 @@ public class PaperTradingService {
|
||||
|
||||
GcPaperAccount account = accountRepo.findById(order.getAccountId()).orElse(null);
|
||||
if (account == null) return;
|
||||
GcAppSettings app = appSettingsService.getEntity(account.getUserId(), account.getDeviceId());
|
||||
if (account.getUserId() == null) return;
|
||||
long uid = account.getUserId();
|
||||
GcAppSettings app = appSettingsService.getEntity(uid, null);
|
||||
|
||||
releaseReservation(account, order);
|
||||
double qty = order.getQuantity().doubleValue();
|
||||
executeTrade(account.getUserId(), account.getDeviceId(), app, order.getSymbol(),
|
||||
executeTrade(uid, app, order.getSymbol(),
|
||||
order.getSide(), order.getOrderKind(), order.getSource(), order.getStrategyId(),
|
||||
limit, qty, null, "STRATEGY".equals(order.getSource()));
|
||||
|
||||
@@ -361,9 +377,10 @@ public class PaperTradingService {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public PaperSummaryDto resetAccount(Long userId, String deviceId) {
|
||||
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId, app);
|
||||
public PaperSummaryDto resetAccount(Long userId) {
|
||||
long uid = TradingAccess.requireUserId(userId);
|
||||
GcAppSettings app = appSettingsService.getEntity(uid, null);
|
||||
GcPaperAccount account = getOrCreateAccount(uid, app);
|
||||
BigDecimal initial = app.getPaperInitialCapital() != null
|
||||
? app.getPaperInitialCapital() : BigDecimal.valueOf(10_000_000);
|
||||
BigDecimal cashBefore = account.getCashBalance();
|
||||
@@ -405,8 +422,8 @@ public class PaperTradingService {
|
||||
accountRepo.save(account);
|
||||
|
||||
allocationService.seedFromPositions(account.getId(), initial);
|
||||
log.info("[Paper] account reset device={} capital={}", deviceId, initial);
|
||||
return getSummary(userId, deviceId, null);
|
||||
log.info("[Paper] account reset userId={} capital={}", uid, initial);
|
||||
return getSummary(uid, null);
|
||||
}
|
||||
|
||||
// ── 내부 ─────────────────────────────────────────────────────────────────
|
||||
@@ -481,11 +498,11 @@ public class PaperTradingService {
|
||||
return Math.max(0, held - reserved);
|
||||
}
|
||||
|
||||
private PaperTradeDto executeTrade(Long userId, String deviceId, GcAppSettings app,
|
||||
private PaperTradeDto executeTrade(long userId, GcAppSettings app,
|
||||
String market, String side, String orderKind, String source,
|
||||
Long strategyId, double inputPrice, double inputQty,
|
||||
String koreanName, boolean autoTrade) {
|
||||
GcPaperAccount account = getOrCreateAccount(userId, deviceId, app);
|
||||
GcPaperAccount account = getOrCreateAccount(userId, app);
|
||||
double feeRate = pct(app.getPaperFeeRatePct(), 0.05);
|
||||
double slip = pct(app.getPaperSlippagePct(), 0);
|
||||
double minOrder = app.getPaperMinOrderKrw() != null
|
||||
@@ -638,14 +655,14 @@ public class PaperTradingService {
|
||||
positionRepo.save(pos);
|
||||
}
|
||||
|
||||
private GcPaperAccount getOrCreateAccount(Long userId, String deviceId, GcAppSettings app) {
|
||||
String dev = deviceId != null && !deviceId.isBlank() ? deviceId : "anonymous";
|
||||
return accountRepo.findByDeviceId(dev).orElseGet(() -> {
|
||||
private GcPaperAccount getOrCreateAccount(long userId, GcAppSettings app) {
|
||||
return accountRepo.findByUserId(userId).orElseGet(() -> {
|
||||
String devKey = TradingAccess.accountDeviceKey(userId);
|
||||
BigDecimal initial = app.getPaperInitialCapital() != null
|
||||
? app.getPaperInitialCapital() : BigDecimal.valueOf(10_000_000);
|
||||
GcPaperAccount a = GcPaperAccount.builder()
|
||||
.userId(userId)
|
||||
.deviceId(dev)
|
||||
.deviceId(devKey)
|
||||
.cashBalance(initial)
|
||||
.realizedPnl(BigDecimal.ZERO)
|
||||
.reservedCash(BigDecimal.ZERO)
|
||||
|
||||
Reference in New Issue
Block a user