Skip to content
Merged

Dev #178

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
ec67bc8
feat:검증 로직 모든 메서드에 추가
bongj9 Jun 7, 2025
4726ccd
refactor:테스트를 위해 메서드 분리(prevRateData)
bongj9 Jun 7, 2025
86e1584
refactor:좀 더 명확하게 get 메서드 정의
bongj9 Jun 7, 2025
5a7f7a7
remove:필요없어진 Repository 삭제
bongj9 Jun 7, 2025
f69edc9
test:MinuteOhlcDataRepository 테스트 생성
bongj9 Jun 7, 2025
eb9ebe3
test:RealTimeTradeRepository 테스트 생성
bongj9 Jun 7, 2025
b37d5ec
test:TradeEventHandler 테스트 생성
bongj9 Jun 7, 2025
ee3b7f2
Merge branch 'dev' of https://github.com/CleanEngine/cleanengine-be i…
bongj9 Jun 8, 2025
32f7385
refactor: 주석 삭제
bongj9 Jun 11, 2025
64d2b40
Merge branch 'dev' of https://github.com/CleanEngine/cleanengine-be i…
bongj9 Jun 11, 2025
6e5fac1
Merge branch 'dev' of https://github.com/CleanEngine/cleanengine-be i…
bongj9 Jun 12, 2025
de3c1f2
fix: h2 호가 생성기 시작 오류
109an94 Jun 14, 2025
d12cae5
test: test 코드 일부 수정 및 삭제
109an94 Jun 14, 2025
3260a86
test: 유저 테스트 코드 mock 동작 추가(오류 수정)
caniro Jun 15, 2025
a3fd7da
Merge pull request #166 from CleanEngine/feat/realitybot-buildfix
109an94 Jun 15, 2025
f5e7695
Merge branch 'dev' of https://github.com/CleanEngine/cleanengine-be i…
bongj9 Jun 15, 2025
8b53497
Merge pull request #167 from CleanEngine/feat/user
caniro Jun 16, 2025
165a27a
feat: ConcurrentSkipListSet기반 큐 구현
Junh-b Jun 16, 2025
7bd17ed
fix: SkipListSet기반 주문큐 정렬 문제 수정
Junh-b Jun 16, 2025
b20ab7e
Merge branch 'dev' of https://github.com/CleanEngine/cleanengine-be i…
bongj9 Jun 16, 2025
83b9c36
temp: 설정내용 임시 반영
Junh-b Jun 17, 2025
6ee8284
refactor:도커 파일 influxdb 추가
bongj9 Jun 17, 2025
03ba381
Merge pull request #169 from CleanEngine/feat/chartdata
Junh-b Jun 17, 2025
aea6707
refactor: validation 으로 변경하였기때문에 False가 아닌 예외를 발생시키는것으로 테스트 변경
bongj9 Jun 17, 2025
4e530ea
Merge branch 'dev' of https://github.com/CleanEngine/cleanengine-be i…
bongj9 Jun 17, 2025
ce437cb
fix : dir 이름 변경
bongj9 Jun 17, 2025
f299807
Merge branch 'dev' into feat/order-pricequeue
Junh-b Jun 18, 2025
3460fe3
config: spanmetrics 설정 추가
Junh-b Jun 18, 2025
713c190
config: jaeger 파일기반으로 변경
Junh-b Jun 18, 2025
bf6857c
Merge pull request #170 from CleanEngine/feat/chartdata
caniro Jun 19, 2025
daba78a
feat: 차트 API 페이징 처리
caniro Jun 20, 2025
609a495
test: 차트 API 페이징 통합테스트 추가
caniro Jun 20, 2025
ae53d52
config: 부하테스트용 지표 설정 변경
Junh-b Jun 20, 2025
f27a9c3
config: 불필요한 지표 필터링
Junh-b Jun 20, 2025
683c457
test: 부하테스트용 token 생성기
Junh-b Jun 20, 2025
6f98e8c
fix: 데이터 초기화 코드 변경
Junh-b Jun 20, 2025
bc5e620
config: 부하테스트용 초기화 설정 추가
Junh-b Jun 20, 2025
f59eca7
Merge pull request #172 from CleanEngine/feat/chart-api
caniro Jun 20, 2025
4fa004e
fix: 실시간 1분봉 데이터 계산 로직 오류 수정
bongj9 Jun 22, 2025
c2a68f2
refactor: 변경된 RealTimeOhlcService 메소드 호출 방식 적용
bongj9 Jun 22, 2025
00597d8
refactor: 신규 구독 시 캐시 데이터를 사용하도록 로직 개선
bongj9 Jun 22, 2025
31e0695
test:새로운 Ohlcv 기능 테스트
bongj9 Jun 23, 2025
ef895e5
Merge pull request #174 from CleanEngine/feat/chartdata
BHyeonKim Jun 23, 2025
10c2898
config: 모니터링 설정 수정
Junh-b Jun 23, 2025
3f4dd5f
fix: 변경된 큐 활성화
Junh-b Jun 23, 2025
cfcc390
Merge branch 'dev' into feat/order-pricequeue
Junh-b Jun 23, 2025
581043c
Merge pull request #175 from CleanEngine/feat/order-pricequeue
Junh-b Jun 23, 2025
6c003f2
fix: 신규 체결 내역 발생 시 주문 실행 (기준치 이내)
109an94 Jun 23, 2025
d644daf
fix: 주문시 apiVWAP 기준 금액 생성
109an94 Jun 23, 2025
8bf7b2e
Merge pull request #177 from CleanEngine/feat/realitybot-chart
caniro Jun 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:2.16.0")
implementation 'org.hibernate.orm:hibernate-micrometer'

implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.google.code.gson:gson:2.13.1'
Expand Down
2 changes: 2 additions & 0 deletions docker/mariadb/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,5 @@ INSERT INTO `if`.asset (ticker, name) VALUES ('TRUMP', '오피셜트럼프');

INSERT INTO `if`.wallet (wallet_id, account_id, buy_price, roi, size, ticker) VALUES (1, 1, 0, 0, 500000000, 'BTC');
INSERT INTO `if`.wallet (wallet_id, account_id, buy_price, roi, size, ticker) VALUES (2, 1, 0, 0, 500000000, 'TRUMP');
INSERT INTO `if`.wallet (wallet_id, account_id, buy_price, roi, size, ticker) VALUES (3, 2, 0, 0, 500000000, 'BTC');
INSERT INTO `if`.wallet (wallet_id, account_id, buy_price, roi, size, ticker) VALUES (4, 2, 0, 0, 500000000, 'TRUMP');
65 changes: 61 additions & 4 deletions monitoring/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ services:
- /etc/localtime:/etc/localtime:ro
- ./opentelemetry-javaagent.jar:/app/opentelemetry-javaagent.jar
working_dir: /app
command: [ "java", "-jar", "coin-0.0.1-SNAPSHOT.jar", "--spring.profiles.active=dev,mariadb-local,actuator,apm" ]
command: [ "java", "-jar", "coin-0.0.1-SNAPSHOT.jar", "--spring.profiles.active=dev,it,mariadb-local,actuator,apm" ]
ports:
- "8080:8080"
env_file:
Expand All @@ -18,14 +18,16 @@ services:
- TZ=Asia/Seoul
- OTEL_SERVICE_NAME=my-spring-app
- OTEL_TRACES_EXPORTER=otlp
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318
- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
- OTEL_LOGS_EXPORTER=none
- OTEL_METRICS_EXPORTER=none
- OTEL_INSTRUMENTATION_METHODS_ENABLED=true
- JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005, -javaagent:/app/opentelemetry-javaagent.jar
- JAVA_TOOL_OPTIONS=-javaagent:/app/opentelemetry-javaagent.jar
depends_on:
mariadb:
condition: service_healthy
otel-collector:
condition: service_started
networks:
- app-network
- monitoring-net
Expand Down Expand Up @@ -60,12 +62,31 @@ services:
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=30d'
- '--web.enable-remote-write-receiver'
ports:
- "9090:9090"
networks:
- monitoring-net
restart: unless-stopped

influxdb:
image: influxdb:2.7
container_name: influxdb
ports:
- "8086:8086"
volumes:
- influxdb_data:/var/lib/influxdb2
environment:
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=k6-user # 임의의 사용자/비밀번호
- DOCKER_INFLUXDB_INIT_PASSWORD=k6-password
- DOCKER_INFLUXDB_INIT_ORG=k6-org
- DOCKER_INFLUXDB_INIT_BUCKET=k6-bucket
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=vJvTx5b8vjgH7mu-M-BsEvdy5_COovexAjyniVhMF1yPzvsp2g8kp62opqXVMq6ICAq2tLJxxD6ifXPBZ9YVmA==
networks:
- monitoring-net
restart: unless-stopped

grafana:
image: grafana/grafana:11.0.0
container_name: grafana
Expand All @@ -80,22 +101,53 @@ services:
depends_on:
- prometheus
- jaeger
- influxdb

jaeger:
image: jaegertracing/all-in-one:latest
container_name: jaeger
user: "${UID}:${GID}"
environment:
- SPAN_STORAGE_TYPE=badger
- BADGER_EPHEMERAL=false
- BADGER_DIRECTORY_VALUE=/tmp/jaeger/data
- BADGER_DIRECTORY_KEY=/tmp/jaeger/keys
- COLLECTOR_OTLP_ENABLED=true
- COLLECTOR_OTLP_GRPC_HOST_PORT=0.0.0.0:4317
- COLLECTOR_OTLP_HTTP_HOST_PORT=0.0.0.0:4318
volumes:
- jaeger_data:/tmp/jaeger
ports:
- "16686:16686"
- "16686:16686" # UI
- "4319:4317"
- "4320:4318"
networks:
- monitoring-net


otel-collector:
image: otel/opentelemetry-collector-contrib:latest
command: ["--config=/etc/otel-collector-config.yml"]
volumes:
- ./otel/otel-collector-config.yml:/etc/otel-collector-config.yml
ports:
- "8889:8889"
- "4317:4317"
- "4318:4318"
- "13133:13133"
networks:
- monitoring-net
depends_on:
jaeger:
condition: service_started

volumes:
prometheus_data: {}
grafana_data: {}
mariadb_data:
driver: local
influxdb_data: {}
jaeger_data: {}

networks:
app-network:
Expand All @@ -104,3 +156,8 @@ networks:
monitoring-net:
name: monitoring-net
driver: bridge


# export K6_INFLUXDB_ORGANIZATION=k6-org
# - export K6_INFLUXDB_BUCKET=k6-bucket
# ./k6 run --out xk6-influxdb=http://localhost:8086 /Users/jangbongjun/coin/src/test/resources/k6/chart-stomp-test.js
47 changes: 47 additions & 0 deletions monitoring/otel/otel-collector-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318

processors:
batch:
timeout: 500ms
send_batch_size: 8192
send_batch_max_size: 16384
probabilistic_sampler:
sampling_percentage: 1

connectors:
spanmetrics:
histogram:
explicit:
buckets: [100us, 1ms, 2ms, 6ms, 10ms, 100ms, 250ms, 500ms, 1s]

exporters:
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true

prometheus:
endpoint: "0.0.0.0:8889"

service:
pipelines:
traces/metrics:
receivers: [otlp]
processors: [batch]
exporters: [spanmetrics]

traces/jaeger:
receivers: [otlp]
processors: [probabilistic_sampler,batch]
exporters: [otlp/jaeger]

metrics:
receivers: [spanmetrics]
processors: [batch]
exporters: [prometheus]
6 changes: 5 additions & 1 deletion monitoring/prometheus/prometheus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ scrape_configs:
- job_name: 'my-app'
static_configs:
- targets: [ 'app:8080' ]
metrics_path: /actuator/prometheus
metrics_path: /actuator/prometheus
- job_name: 'otel-collector'
scrape_interval: 15s
static_configs:
- targets: [ 'otel-collector:8889' ]
Original file line number Diff line number Diff line change
Expand Up @@ -37,51 +37,35 @@ public void publishRealTimeOhlc() {
try {
log.debug("△ 실시간 OHLC 데이터 스케줄러 실행");

// 구독된 티커가 없으면 조기 종료
if (subscriptionService.getAllRealTimeOhlcSubscribedTickers().isEmpty()) {
log.debug("실시간 OHLC 구독된 티커 없음, 전송 생략");
return;
}
final LocalDateTime now = LocalDateTime.now();

// 모든 구독된 티커에 대해 데이터 전송
for (String ticker : subscriptionService.getAllRealTimeOhlcSubscribedTickers()) {
try {
log.debug("티커 {} 실시간 OHLC 데이터 전송 중...", ticker);

// 티커별 최신 OHLC 데이터 조회 및 전송
RealTimeOhlcDto ohlcData = realTimeOhlcService.getRealTimeOhlc(ticker);
RealTimeOhlcDto ohlcData = realTimeOhlcService.getAndUpdateCumulative1mOhlc(ticker, now);

if (ohlcData == null) {
// 이전에 전송한 데이터가 있는지 확인
RealTimeOhlcDto lastSentData = lastSentOhlcDataMap.get(ticker);

if (lastSentData != null) {
// 이전 데이터가 있으면 타임스탬프만 업데이트하여 재사용
log.debug("티커 {}의 실시간 OHLC 데이터가 없습니다. 이전 데이터 재사용", ticker);
RealTimeOhlcDto updatedData = new RealTimeOhlcDto(lastSentData.getTicker(), LocalDateTime.now(), // 현재 시간으로 업데이트
RealTimeOhlcDto updatedData = new RealTimeOhlcDto(lastSentData.getTicker(), now,
lastSentData.getOpen(), lastSentData.getHigh(), lastSentData.getLow(), lastSentData.getClose(), lastSentData.getVolume());

messagingTemplate.convertAndSend("/topic/realTimeOhlc/" + ticker, updatedData);
lastSentOhlcDataMap.put(ticker, updatedData); // 캐시 업데이트
lastSentOhlcDataMap.put(ticker, updatedData);
} else {
// 이전 데이터도 없는 경우 빈 데이터 전송 (첫 구독 시)
log.debug("티커 {}의 이전 OHLC 데이터도 없습니다. 빈 데이터 전송", ticker);
RealTimeOhlcDto emptyData = new RealTimeOhlcDto();
emptyData.setTicker(ticker);
emptyData.setTimestamp(LocalDateTime.now());
emptyData.setOpen(0.0);
emptyData.setHigh(0.0);
emptyData.setLow(0.0);
emptyData.setClose(0.0);
emptyData.setVolume(0.0);

RealTimeOhlcDto emptyData = new RealTimeOhlcDto(ticker, now, 0.0, 0.0, 0.0, 0.0, 0.0);
messagingTemplate.convertAndSend("/topic/realTimeOhlc/" + ticker, emptyData);
lastSentOhlcDataMap.put(ticker, emptyData); // 캐시 업데이트
lastSentOhlcDataMap.put(ticker, emptyData);
}
} else {
// 조회된 실시간 OHLC 데이터 전송
messagingTemplate.convertAndSend("/topic/realTimeOhlc/" + ticker, ohlcData);
lastSentOhlcDataMap.put(ticker, ohlcData); // 캐시 업데이트
lastSentOhlcDataMap.put(ticker, ohlcData);
log.debug("실시간 OHLC 데이터 전송: {}", ohlcData);
}
} catch (Exception e) {
Expand All @@ -91,8 +75,5 @@ public void publishRealTimeOhlc() {
} catch (Exception e) {
log.error("△ 실시간 OHLC 데이터 발행 중 오류: {}", e.getMessage(), e);
}

}


}
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
package com.cleanengine.coin.chart.controller;

import com.cleanengine.coin.chart.dto.RealTimeOhlcDto;
import com.cleanengine.coin.chart.service.minute.MinuteOhlcDataService;
import com.cleanengine.coin.chart.service.minute.PagingMinuteOhlcDataService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;

@RestController
@RequestMapping("/api/minute-ohlc")
@RequiredArgsConstructor
public class MinuteOhlcDataController {

private final MinuteOhlcDataService service;
private final PagingMinuteOhlcDataService service;

/**
* GET /api/minute-ohlc?ticker=BTC
* DB에 있는 과거 거래를 1분 단위로 묶어 OHLC+volume을 계산한 리스트 반환
* GET /api/minute-ohlc?ticker=BTC&count=100&interval=1&from=2025-06-19T10:30
* DB에 있는 과거 거래를 interval 단위로 묶어 OHLC+volume을 계산한 리스트 반환
*/
@GetMapping
public ResponseEntity<List<RealTimeOhlcDto>> getMinuteOhlc(
@RequestParam("ticker") String ticker
@RequestParam("ticker") String ticker,
@RequestParam(value = "count", defaultValue = "100") int count,
@RequestParam(value = "interval", defaultValue = "1") int interval,
@RequestParam(value = "from", required = false) LocalDateTime from
) {
List<RealTimeOhlcDto> data = service.getMinuteOhlcData(ticker);
if (from == null) {
from = LocalDateTime.now();
}

List<RealTimeOhlcDto> data = service.getMinuteOhlcData(ticker, count, interval, from.minusMinutes(1));
return ResponseEntity.ok(data);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.cleanengine.coin.chart.dto.RealTimeOhlcDto;
import com.cleanengine.coin.chart.service.ChartSubscriptionService;
import com.cleanengine.coin.chart.service.RealTimeOhlcService;
import com.cleanengine.coin.chart.service.RealTimeOhlcService; // RealTimeOhlcService 의존성은 이제 불필요
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
Expand All @@ -19,9 +19,8 @@
public class WebSocketMessageController {

private final ChartSubscriptionService subscriptionService;
private final RealTimeOhlcService realTimeOhlcService;
private final SimpMessagingTemplate messagingTemplate;
private final ChartDataController chartDataController;
private final ChartDataController chartDataController; // 이미 계산된 데이터를 가진 컨트롤러를 활용

/**
* 실시간 OHLC 데이터 구독 처리
Expand All @@ -34,30 +33,16 @@ public void subscribeRealTimeOhlc(RealTimeTradeMappingDto request) {
// 구독 목록에 추가
subscriptionService.subscribeRealTimeOhlc(ticker);

// 구독 즉시 최근 실시간 OHLC 데이터 전송
RealTimeOhlcDto latestOhlcData = realTimeOhlcService.getRealTimeOhlc(ticker);

RealTimeOhlcDto lastSentData = chartDataController.getLastSentOhlcDataMap().get(ticker);

if (latestOhlcData == null) {
if (lastSentData != null) {
// 이전에 전송한 데이터가 있으면 재사용
log.debug("티커 {}의 실시간 OHLC 데이터가 없습니다. 이전 데이터 재사용", ticker);
messagingTemplate.convertAndSend("/topic/realTimeOhlc/" + ticker, lastSentData);
} else {
// 이전 데이터도 없는 경우 빈 데이터 전송
log.debug("티커 {}의 실시간 OHLC 데이터가 없습니다. 빈 데이터 전송", ticker);
RealTimeOhlcDto emptyData = createEmptyRealTimeOhlcDto(ticker);
messagingTemplate.convertAndSend("/topic/realTimeOhlc/" + ticker, emptyData);
// 빈 데이터도 캐시에 저장
chartDataController.getLastSentOhlcDataMap().put(ticker, emptyData);
}
if (lastSentData == null) {
log.debug("티커 {}의 캐시된 OHLC 데이터가 없습니다. 빈 데이터 전송", ticker);
RealTimeOhlcDto emptyData = createEmptyRealTimeOhlcDto(ticker);
messagingTemplate.convertAndSend("/topic/realTimeOhlc/" + ticker, emptyData);
} else {
log.debug("티커 {}의 실시간 OHLC 데이터 전송: {}", ticker, latestOhlcData);
messagingTemplate.convertAndSend("/topic/realTimeOhlc/" + ticker, latestOhlcData);
// 데이터 캐시에 저장
chartDataController.getLastSentOhlcDataMap().put(ticker, latestOhlcData);

// 캐시된 데이터가 있으면 즉시 전송
log.debug("티커 {}의 캐시된 OHLC 데이터 즉시 전송: {}", ticker, lastSentData);
messagingTemplate.convertAndSend("/topic/realTimeOhlc/" + ticker, lastSentData);
}
}

Expand All @@ -80,6 +65,5 @@ private RealTimeOhlcDto createEmptyRealTimeOhlcDto(String ticker) {
@Getter
public static class RealTimeTradeMappingDto {
private String ticker;

}
}
Loading
Loading