모의투자 로직 변경

This commit is contained in:
Macbook
2026-05-31 14:05:46 +09:00
parent ead97dad5d
commit d6eedf19bb
33 changed files with 1456 additions and 330 deletions
@@ -8,6 +8,7 @@ import com.goldenchart.dto.UpbitApiCredentials;
import com.goldenchart.entity.GcAppSettings;
import com.goldenchart.entity.GcLiveTrade;
import com.goldenchart.repository.GcLiveTradeRepository;
import com.goldenchart.trading.TradingAccess;
import com.goldenchart.trading.TradingMode;
import com.goldenchart.upbit.UpbitOrderApiClient;
import lombok.RequiredArgsConstructor;
@@ -36,8 +37,9 @@ public class LiveTradingService {
private final GcLiveTradeRepository tradeRepo;
@Transactional(readOnly = true)
public LiveSummaryDto getSummary(Long userId, String deviceId) {
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
public LiveSummaryDto getSummary(Long userId) {
long uid = TradingAccess.requireUserId(userId);
GcAppSettings app = appSettingsService.getEntity(uid, null);
UpbitApiCredentials creds = appSettingsService.resolveUpbitCredentials(app);
boolean configured = creds.isComplete();
double krw = 0;
@@ -76,22 +78,27 @@ public class LiveTradingService {
}
@Transactional(readOnly = true)
public List<LiveTradeDto> listTrades(Long userId, String deviceId) {
String dev = resolveDeviceId(deviceId);
return tradeRepo.findTop100ByDeviceIdOrderByCreatedAtDesc(dev)
public List<LiveTradeDto> listTrades(Long userId) {
long uid = TradingAccess.requireUserId(userId);
return tradeRepo.findTop100ByUserIdOrderByCreatedAtDesc(uid)
.stream().map(this::toDto).toList();
}
/**
* 전략 시그널·SL/TP 에서 호출.
*/
public void tryExecuteOnSignal(String deviceId, Long userId, String market,
public void tryExecuteOnSignal(Long userId, String market,
Long strategyId, String signalType, double price,
String source) {
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);
TradingMode mode = TradingMode.fromString(app.getTradingMode());
if (!mode.useLive()) return;
if (!Boolean.TRUE.equals(app.getLiveAutoTradeEnabled())) return;
@@ -118,7 +125,7 @@ public class LiveTradingService {
return;
}
JsonNode res = upbitApi.placeMarketBuy(access, secret, market, budget);
recordTrade(deviceId, userId, market, "BUY", source, strategyId, res, price, budget);
recordTrade(uid, market, "BUY", source, strategyId, res, price, budget);
log.info("[LiveTrading] BUY {} ~{} KRW", market, (long) budget);
} else {
double vol = upbitApi.getCoinBalance(access, secret, market);
@@ -127,7 +134,7 @@ public class LiveTradingService {
return;
}
JsonNode res = upbitApi.placeMarketSell(access, secret, market, vol);
recordTrade(deviceId, userId, market, "SELL", source, strategyId, res, price, vol);
recordTrade(uid, market, "SELL", source, strategyId, res, price, vol);
log.info("[LiveTrading] SELL {} vol={}", market, vol);
}
} catch (UpbitOrderApiClient.UpbitApiException e) {
@@ -138,11 +145,12 @@ public class LiveTradingService {
/**
* 알림 모달·수동 주문 — 시장가 매수/매도.
*/
public LiveTradeDto placeManualOrder(Long userId, String deviceId, LiveOrderRequest req) {
public LiveTradeDto placeManualOrder(Long userId, LiveOrderRequest req) {
long uid = TradingAccess.requireUserId(userId);
if (req == null || req.getMarket() == null || req.getSide() == null) {
throw new IllegalArgumentException("market, side required");
}
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
GcAppSettings app = appSettingsService.getEntity(uid, null);
UpbitApiCredentials creds = appSettingsService.resolveUpbitCredentials(app);
if (!creds.isComplete()) {
throw new IllegalStateException("업비트 API 키가 등록되지 않았습니다.");
@@ -151,8 +159,6 @@ public class LiveTradingService {
String secret = creds.secretKey();
String market = req.getMarket();
String side = req.getSide().toUpperCase();
String dev = resolveDeviceId(deviceId);
try {
if ("BUY".equals(side)) {
double budget = req.getKrwAmount() != null && req.getKrwAmount() > 0
@@ -162,8 +168,8 @@ public class LiveTradingService {
throw new IllegalStateException("주문 가능 금액이 부족합니다.");
}
JsonNode res = upbitApi.placeMarketBuy(access, secret, market, budget);
recordTrade(dev, userId, market, "BUY", "MANUAL", req.getStrategyId(), res, 0, budget);
return listTrades(userId, deviceId).get(0);
recordTrade(uid, market, "BUY", "MANUAL", req.getStrategyId(), res, 0, budget);
return listTrades(uid).get(0);
}
if ("SELL".equals(side)) {
double vol = req.getQuantity() != null && req.getQuantity() > 0
@@ -171,8 +177,8 @@ public class LiveTradingService {
: upbitApi.getCoinBalance(access, secret, market);
if (vol <= 0) throw new IllegalStateException("매도 가능 수량이 없습니다.");
JsonNode res = upbitApi.placeMarketSell(access, secret, market, vol);
recordTrade(dev, userId, market, "SELL", "MANUAL", req.getStrategyId(), res, 0, vol);
return listTrades(userId, deviceId).get(0);
recordTrade(uid, market, "SELL", "MANUAL", req.getStrategyId(), res, 0, vol);
return listTrades(uid).get(0);
}
throw new IllegalArgumentException("side must be BUY or SELL");
} catch (UpbitOrderApiClient.UpbitApiException e) {
@@ -198,7 +204,7 @@ public class LiveTradingService {
return appSettingsService.hasUpbitApiKeys(app);
}
private void recordTrade(String deviceId, Long userId, String market, String side,
private void recordTrade(long userId, String market, String side,
String source, Long strategyId, JsonNode res,
double refPrice, double amountOrVol) {
String uuid = res != null ? res.path("uuid").asText(null) : null;
@@ -209,7 +215,7 @@ public class LiveTradingService {
: BigDecimal.valueOf(refPrice * executed).setScale(2, RM);
tradeRepo.save(GcLiveTrade.builder()
.deviceId(resolveDeviceId(deviceId))
.deviceId(TradingAccess.accountDeviceKey(userId))
.userId(userId)
.symbol(market)
.side(side)
@@ -239,7 +245,4 @@ public class LiveTradingService {
.build();
}
private static String resolveDeviceId(String deviceId) {
return deviceId != null && !deviceId.isBlank() ? deviceId : "anonymous";
}
}