모의투자 로직 변경

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
@@ -0,0 +1,26 @@
package com.goldenchart.trading;
/**
* 로그인 사용자(userId) 전용 매매·모의·전략 접근 규칙.
* 게스트(앱 입장만 한 비로그인)는 deviceId만으로 매매 기능을 사용할 수 없다.
*/
public final class TradingAccess {
private TradingAccess() {}
/** 모의 계좌 device_id 컬럼(NOT NULL UK)용 합성 키 — 실제 기기와 무관 */
public static String accountDeviceKey(long userId) {
return "user:" + userId;
}
public static long requireUserId(Long userId) {
if (userId == null || userId <= 0) {
throw new TradingAccessDeniedException();
}
return userId;
}
public static boolean isRegisteredUser(Long userId) {
return userId != null && userId > 0;
}
}
@@ -0,0 +1,22 @@
package com.goldenchart.trading;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* 게스트(비로그인) 또는 미등록 사용자가 매매·모의·전략 API에 접근할 때.
*/
@ResponseStatus(HttpStatus.FORBIDDEN)
public class TradingAccessDeniedException extends RuntimeException {
public static final String DEFAULT_MESSAGE =
"매매·모의투자·투자전략·자동매매 기능은 정식 로그인 후 이용할 수 있습니다.";
public TradingAccessDeniedException() {
super(DEFAULT_MESSAGE);
}
public TradingAccessDeniedException(String message) {
super(message);
}
}
@@ -2,6 +2,7 @@ package com.goldenchart.trading;
import com.goldenchart.entity.GcAppSettings;
import com.goldenchart.service.AppSettingsService;
import com.goldenchart.trading.TradingAccess;
import com.goldenchart.service.LiveTradingService;
import com.goldenchart.service.PaperTradingService;
import com.goldenchart.trading.pipeline.OrderRequest;
@@ -21,32 +22,34 @@ public class TradingExecutionService {
private final PaperTradingService paperTradingService;
private final LiveTradingService liveTradingService;
public void executeSignal(String deviceId, Long userId, String market,
public void executeSignal(Long userId, String market,
Long strategyId, String side, double price) {
GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
if (!TradingAccess.isRegisteredUser(userId)) return;
GcAppSettings app = appSettingsService.getEntity(userId, null);
TradingMode mode = TradingMode.fromString(app.getTradingMode());
if (mode.usePaper() && Boolean.TRUE.equals(app.getPaperTradingEnabled())) {
paperTradingService.tryExecuteOnSignal(deviceId, userId, market, strategyId, side, price);
paperTradingService.tryExecuteOnSignal(userId, market, strategyId, side, price);
}
if (mode.useLive()) {
liveTradingService.tryExecuteOnSignal(
deviceId, userId, market, strategyId, side, price, "STRATEGY");
userId, market, strategyId, side, price, "STRATEGY");
}
}
public void executeRiskExit(OrderRequest req) {
GcAppSettings app = appSettingsService.getEntity(req.userId(), req.deviceId());
if (!TradingAccess.isRegisteredUser(req.userId())) return;
GcAppSettings app = appSettingsService.getEntity(req.userId(), null);
TradingMode mode = TradingMode.fromString(app.getTradingMode());
String source = req.kind() == OrderRequest.OrderKind.STOP_LOSS ? "STOP_LOSS" : "TAKE_PROFIT";
if (mode.usePaper() && Boolean.TRUE.equals(app.getPaperTradingEnabled())) {
paperTradingService.tryExecuteOnSignal(
req.deviceId(), req.userId(), req.market(), null, "SELL", req.price());
req.userId(), req.market(), null, "SELL", req.price());
}
if (mode.useLive()) {
liveTradingService.tryExecuteOnSignal(
req.deviceId(), req.userId(), req.market(), null, "SELL", req.price(), source);
req.userId(), req.market(), null, "SELL", req.price(), source);
}
}
}
@@ -1,5 +1,6 @@
package com.goldenchart.trading.pipeline;
import com.goldenchart.trading.TradingAccess;
import com.goldenchart.trading.TradingExecutionService;
import com.google.common.util.concurrent.RateLimiter;
import jakarta.annotation.PostConstruct;
@@ -64,9 +65,11 @@ public class OrderExecutionQueue {
}
}
public void submitSignal(String deviceId, Long userId, String market,
public void submitSignal(Long userId, String market,
Long strategyId, String signal, double price) {
submit(OrderRequest.strategySignal(deviceId, userId, market, strategyId, signal, price));
if (!TradingAccess.isRegisteredUser(userId)) return;
submit(OrderRequest.strategySignal(
TradingAccess.accountDeviceKey(userId), userId, market, strategyId, signal, price));
}
private void runLoop() {
@@ -88,12 +91,12 @@ public class OrderExecutionQueue {
}
private void executeNow(OrderRequest req) {
if (req.deviceId() == null || req.deviceId().isBlank()) return;
if (!TradingAccess.isRegisteredUser(req.userId())) return;
if (!"BUY".equals(req.side()) && !"SELL".equals(req.side())) return;
try {
if (req.kind() == OrderRequest.OrderKind.STRATEGY_SIGNAL) {
tradingExecutionService.executeSignal(
req.deviceId(), req.userId(), req.market(),
req.userId(), req.market(),
req.strategyId(), req.side(), req.price());
} else {
tradingExecutionService.executeRiskExit(req);
@@ -8,6 +8,7 @@ import com.goldenchart.repository.GcPaperAccountRepository;
import com.goldenchart.repository.GcPaperPositionRepository;
import com.goldenchart.service.AppSettingsService;
import com.goldenchart.service.BacktestSettingsService;
import com.goldenchart.trading.TradingAccess;
import com.goldenchart.trading.TradingExecutionService;
import com.goldenchart.trading.TradingMode;
import lombok.RequiredArgsConstructor;
@@ -53,13 +54,12 @@ public class RealtimePositionMonitor {
GcPaperAccount account = accountRepo.findById(pos.getAccountId()).orElse(null);
if (account == null) continue;
String deviceId = account.getDeviceId();
Long userId = account.getUserId();
if (deviceId == null || deviceId.isBlank()) continue;
if (userId == null || userId <= 0) continue;
GcAppSettings app;
try {
app = appSettingsService.getEntity(userId, deviceId);
app = appSettingsService.getEntity(userId, null);
} catch (Exception e) {
continue;
}
@@ -75,7 +75,7 @@ public class RealtimePositionMonitor {
double avg = pos.getAvgPrice().doubleValue();
if (avg <= 0) continue;
BacktestSettingsDto risk = backtestSettingsService.get(deviceId);
BacktestSettingsDto risk = backtestSettingsService.get(TradingAccess.accountDeviceKey(userId));
double pnlPct = (tradePrice - avg) / avg * 100.0;
if (Boolean.TRUE.equals(risk.getStopLossEnabled())
@@ -84,7 +84,7 @@ public class RealtimePositionMonitor {
lastExitMs.put(cooldownKey, now);
double sl = risk.getStopLossPct().doubleValue();
tradingExecutionService.executeRiskExit(
OrderRequest.stopLoss(deviceId, userId, market, tradePrice, sl));
OrderRequest.stopLoss(TradingAccess.accountDeviceKey(userId), userId, market, tradePrice, sl));
log.info("[SL] {} @ {} pnl={}% (limit -{}%)", market, tradePrice,
String.format("%.2f", pnlPct), sl);
return;
@@ -96,7 +96,7 @@ public class RealtimePositionMonitor {
lastExitMs.put(cooldownKey, now);
double tp = risk.getTakeProfitPct().doubleValue();
tradingExecutionService.executeRiskExit(
OrderRequest.takeProfit(deviceId, userId, market, tradePrice, tp));
OrderRequest.takeProfit(TradingAccess.accountDeviceKey(userId), userId, market, tradePrice, tp));
log.info("[TP] {} @ {} pnl={}% (target +{}%)", market, tradePrice,
String.format("%.2f", pnlPct), tp);
return;