Skip to content

Commit fdddf98

Browse files
authored
Merge pull request #131 from CleanEngine/dev
Dev
2 parents 7848251 + e17e715 commit fdddf98

59 files changed

Lines changed: 1994 additions & 516 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ repositories {
2525
mavenCentral()
2626
}
2727

28+
2829
jacoco { //추가함
2930
toolVersion = "0.8.13"
3031
}

src/main/java/com/cleanengine/coin/CoinApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
import jakarta.annotation.PostConstruct;
44
import org.springframework.boot.SpringApplication;
55
import org.springframework.boot.autoconfigure.SpringBootApplication;
6+
import org.springframework.scheduling.annotation.EnableAsync;
67
import org.springframework.scheduling.annotation.EnableScheduling;
78

89
import java.util.TimeZone;
910

1011

1112
@EnableScheduling
13+
@EnableAsync
1214
@SpringBootApplication
1315
public class CoinApplication {
1416

src/main/java/com/cleanengine/coin/realitybot/api/ApiScheduler.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
import com.cleanengine.coin.common.annotation.WorkingServerProfile;
44
import com.cleanengine.coin.order.adapter.out.persistentce.asset.AssetRepository;
55
import com.cleanengine.coin.order.domain.Asset;
6+
import com.cleanengine.coin.realitybot.domain.APIVWAPState;
67
import com.cleanengine.coin.realitybot.dto.Ticks;
7-
import com.cleanengine.coin.realitybot.service.ApiVWAPService;
8+
import com.cleanengine.coin.realitybot.parser.TickParser;
89
import com.cleanengine.coin.realitybot.service.OrderGenerateService;
9-
import com.cleanengine.coin.realitybot.service.TickParser;
1010
import com.cleanengine.coin.realitybot.service.TickServiceManager;
1111
import lombok.RequiredArgsConstructor;
1212
import lombok.extern.slf4j.Slf4j;
13-
import org.springframework.scheduling.annotation.Scheduled;
1413
import org.springframework.stereotype.Component;
1514

1615
import java.util.List;
@@ -29,38 +28,40 @@ public class ApiScheduler {
2928
private final TickServiceManager tickServiceManager;
3029
private final Map<String,Long> lastSequentialIdMap = new ConcurrentHashMap<>();
3130
private final AssetRepository assetRepository;
31+
private final CoinoneAPIClient coinoneAPIClient;
3232
private String ticker;
3333

34-
@Scheduled(fixedRate = 5000)
34+
// @Scheduled(fixedRate = 5000)
3535
public void MarketAllRequest() throws InterruptedException {
3636
List<Asset> tickers = assetRepository.findAll();
3737
for (Asset ticker : tickers){
3838
String tickerName = ticker.getTicker();
3939
MarketDataRequest(tickerName);
40-
Thread.sleep(500);
40+
// Thread.sleep(500);
4141
}
4242
}
4343

4444
public void MarketDataRequest(String ticker){
4545
this.ticker = ticker;
4646
String rawJson = bithumbAPIClient.get(ticker); //api raw데이터
47-
List<Ticks> gson = TickParser.parseGson(rawJson); //json을 list로 변환
47+
// String rawJson = getMarketDataWithFallback(ticker);
48+
List<Ticks> gson = tickParser.parseGson(rawJson); //json을 list로 변환
4849

49-
ApiVWAPService apiVWAPService = tickServiceManager.getService(ticker);
50+
APIVWAPState apiVWAPState = tickServiceManager.getService(ticker);
5051
long lastSeqId = lastSequentialIdMap.getOrDefault(ticker,0L);
5152

5253
//api 중복검사하여 queue에 저장하기
5354
for (int i = gson.size()-1; i >=0 ; i--) {//2차 : 10 - 역순으로 정렬되어 - 순회해야 함.
5455
Ticks ticks = gson.get(i);
5556
if (ticks.getSequential_id() > lastSeqId){ //중복 검증용
56-
apiVWAPService.addTick(ticks);
57+
apiVWAPState.addTick(ticks);
5758
lastSeqId = Math.max(lastSeqId, ticks.getSequential_id()); //중복 id 갱신
5859

5960
}
6061
}
6162
lastSequentialIdMap.put(ticker,lastSeqId);
62-
double vwap = apiVWAPService.getVWAP();
63-
double volume = apiVWAPService.getAvgVolumePerOrder();
63+
double vwap = apiVWAPState.getVWAP();
64+
double volume = apiVWAPState.getAvgVolumePerOrder();
6465
orderGenerateService.generateOrder(ticker,vwap,volume); //1tick 당 매수/매도 3개씩 제작
6566
// log.info("작동확인 {}의 가격 : {} , 볼륨 : {}",ticker, vwap, volume);
6667
}
@@ -73,6 +74,23 @@ public void destroy() throws Exception { //담긴 Queue데이터 확인용
7374
// orderQueueManagerService.logAllOrders();
7475
// virtualTradeService.printOrderSummary();
7576
}*/
77+
/*public String getMarketDataWithFallback(String ticker) {
78+
try {
79+
// String bithumbJson = bithumbAPIClient.get(ticker);
80+
String bithumbJson = null;
7681
82+
// 예외가 없었어도 비정상 응답일 수 있음 → 예: 빈 JSON 또는 에러 코드
83+
if (bithumbJson == null || bithumbJson.isBlank() || bithumbJson.contains("\"result\":\"error\"")) {
84+
log.warn("Bithumb 응답 비정상, Coinone으로 대체 요청");
85+
return coinoneAPIClient.get(ticker);
86+
}
87+
88+
return bithumbJson;
89+
90+
} catch (Exception e) {
91+
log.error("Bithumb API 오류 발생: {} → Coinone으로 대체 요청", e.getMessage());
92+
return coinoneAPIClient.get(ticker);
93+
}
94+
}*/
7795

7896
}

src/main/java/com/cleanengine/coin/realitybot/api/BithumbAPIClient.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,41 @@
44
import lombok.RequiredArgsConstructor;
55
import lombok.extern.slf4j.Slf4j;
66
import okhttp3.OkHttpClient;
7-
import okhttp3.*;
7+
import okhttp3.Request;
8+
import okhttp3.Response;
89
import org.springframework.stereotype.Component;
9-
import com.google.gson.Gson;
1010

1111
import java.io.IOException;
1212

1313
@Component
1414
@RequiredArgsConstructor
1515
@Slf4j
1616
public class BithumbAPIClient {
17-
private OkHttpClient client;
18-
private Gson gson;
17+
private final OkHttpClient client;
1918
private String ticker;
2019

2120

2221
public String get(String ticker){ //API를 responseBody에 담아 반환
2322
this.ticker = ticker;
24-
client = new OkHttpClient();
25-
gson = new Gson();
23+
// client = new OkHttpClient();
24+
// gson = new Gson();
25+
26+
// try {
27+
// Thread.sleep(2500);
28+
// } catch (InterruptedException e) {
29+
// Thread.currentThread().interrupt();
30+
// }
31+
32+
2633
Request request = new Request.Builder()
2734
.url("https://api.bithumb.com/v1/trades/ticks?market=krw-"+ticker+"&count=10")
2835
.get()
2936
.addHeader("accept", "application/json")
3037
.build();
3138
try (Response response = client.newCall(request).execute()){
39+
if ((response.code() == 400)){
40+
log.warn("DB asset 최신화가 필요합니다 : {}",ticker);
41+
}
3242
String responseBody = response.body().string();
3343
// return gson.toJson(response.body().string());
3444
log.debug("{}의 Bithumb API 응답 : {}",ticker,responseBody);
@@ -37,5 +47,24 @@ public String get(String ticker){ //API를 responseBody에 담아 반환
3747
throw new RuntimeException(e);
3848
}
3949
}
50+
public String getOpeningPrice(String ticker){
51+
this.ticker = ticker;
52+
Request request = new Request.Builder()
53+
.url("https://api.bithumb.com/v1/ticker?markets=KRW-"+ticker)
54+
.get()
55+
.addHeader("accept", "application/json")
56+
.build();
57+
try (Response response = client.newCall(request).execute()){
58+
if ((response.code() == 400)){
59+
log.warn("DB asset 최신화가 필요합니다 : {}",ticker);
60+
}
61+
String responseBody = response.body().string();
62+
// return gson.toJson(response.body().string());
63+
log.debug("{}의 OpeningPirce 응답 : {}",ticker,responseBody);
64+
return responseBody;
65+
} catch (IOException e) {
66+
throw new RuntimeException("API 요청 중 예외 발생",e);
67+
}
68+
}
4069

4170
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.cleanengine.coin.realitybot.api;
2+
3+
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.extern.slf4j.Slf4j;
6+
import okhttp3.OkHttpClient;
7+
import okhttp3.Request;
8+
import okhttp3.Response;
9+
import org.springframework.stereotype.Component;
10+
11+
import java.io.IOException;
12+
13+
@Component
14+
@RequiredArgsConstructor
15+
@Slf4j
16+
public class CoinoneAPIClient {
17+
private final OkHttpClient client;
18+
private String ticker;
19+
20+
21+
public String get(String ticker){ //API를 responseBody에 담아 반환
22+
this.ticker = ticker;
23+
Request request = new Request.Builder()
24+
.url("https://api.coinone.co.kr/public/v2/trades/KRW/"+ticker+"?size=10")
25+
.get()
26+
.addHeader("accept", "application/json")
27+
.build();
28+
try (Response response = client.newCall(request).execute()){
29+
if ((response.code() == 400)){
30+
log.warn("잘못된 ticker를 입력하였습니다. 입력된 ticker : {}",ticker);
31+
}
32+
String responseBody = response.body().string();
33+
// return gson.toJson(response.body().string());
34+
log.info("{}의 Bithumb API 응답 : {}",ticker,responseBody);
35+
return responseBody;
36+
} catch (IOException e) {
37+
throw new RuntimeException(e);
38+
}
39+
}
40+
/* public String getOpeningPrice(String ticker){
41+
this.ticker = ticker;
42+
Request request = new Request.Builder()
43+
.url("https://api.bithumb.com/v1/ticker?markets=KRW-"+ticker)
44+
.get()
45+
.addHeader("accept", "application/json")
46+
.build();
47+
try (Response response = client.newCall(request).execute()){
48+
if ((response.code() == 400)){
49+
log.warn("잘못된 ticker를 입력하였습니다. 입력된 ticker : {}",ticker);
50+
}
51+
String responseBody = response.body().string();
52+
// return gson.toJson(response.body().string());
53+
log.debug("{}의 OpeningPirce 응답 : {}",ticker,responseBody);
54+
return responseBody;
55+
} catch (IOException e) {
56+
throw new RuntimeException("API 요청 중 예외 발생",e);
57+
}
58+
}*/
59+
60+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.cleanengine.coin.realitybot.api;
2+
3+
import com.cleanengine.coin.order.adapter.out.persistentce.asset.AssetRepository;
4+
import com.cleanengine.coin.order.domain.Asset;
5+
import com.cleanengine.coin.realitybot.dto.OpeningPrice;
6+
import com.cleanengine.coin.realitybot.parser.OpeningPriceParser;
7+
import com.cleanengine.coin.realitybot.vo.UnitPricePolicy;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.boot.ApplicationArguments;
11+
import org.springframework.boot.ApplicationRunner;
12+
import org.springframework.scheduling.annotation.Scheduled;
13+
import org.springframework.stereotype.Service;
14+
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.concurrent.ConcurrentHashMap;
18+
19+
@Slf4j
20+
@Service
21+
@RequiredArgsConstructor
22+
public class UnitPriceRefresher implements ApplicationRunner {
23+
private final UnitPricePolicy unitPricePolicy;
24+
private final AssetRepository assetRepository;
25+
private final BithumbAPIClient bithumbAPIClient;
26+
private final OpeningPriceParser openingPriceParser;
27+
private final Map<String ,Double> unitPriceCache = new ConcurrentHashMap<>();
28+
29+
@Override
30+
public void run(ApplicationArguments args){
31+
log.info("Running Unit Price Refresher...");
32+
initializeUnitPrices();
33+
}
34+
35+
public void initializeUnitPrices() {
36+
List<Asset> tickers = assetRepository.findAll();
37+
for (Asset ticker : tickers){
38+
double unitPrice = fetchOpeningPriceFromAPI(ticker.getTicker());
39+
unitPriceCache.put(ticker.getTicker(),unitPrice);
40+
}
41+
}
42+
43+
@Scheduled(cron = "${bot-handler.corn}")
44+
public void refreshUnitPrices() {
45+
initializeUnitPrices();
46+
// List<Asset> tickers = assetRepository.findAll();
47+
// for (Asset ticker : tickers){
48+
// double unitPrice = fetchOpeningPriceFromAPI(ticker.getTicker());
49+
// unitPriceCache.put(ticker.getTicker(),unitPrice);
50+
// }
51+
52+
}
53+
54+
private double fetchOpeningPriceFromAPI(String ticker) {
55+
String rawJson = bithumbAPIClient.getOpeningPrice(ticker); //api raw데이터
56+
OpeningPrice json = openingPriceParser.parseGson(rawJson); //json을 list로 변환
57+
double unitprice = unitPricePolicy.getUnitPrice(json.getOpening_price());
58+
return unitprice;
59+
}
60+
61+
public double getUnitPriceByTicker(String ticker){
62+
return unitPriceCache.get(ticker);
63+
}
64+
}

src/main/java/com/cleanengine/coin/realitybot/config/ApiClientConfig.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,4 @@ public OkHttpClient okHttpClient() {
1616
// .addInterceptor()
1717
.build();
1818
}
19-
20-
@Bean
21-
public Queue<Ticks> ticksQueue(){ //공통화 시킴
22-
return new LinkedList<>();
23-
}
2419
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.cleanengine.coin.realitybot.config;
2+
3+
import com.cleanengine.coin.realitybot.api.ApiScheduler;
4+
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.scheduling.annotation.EnableScheduling;
7+
import org.springframework.scheduling.annotation.SchedulingConfigurer;
8+
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
9+
10+
import java.time.Duration;
11+
12+
@Configuration
13+
@EnableScheduling
14+
//@RequiredArgsConstructor
15+
public class SchedulerConfig implements SchedulingConfigurer {
16+
17+
//멀티쓰레드 환경 x
18+
// @Autowired
19+
// private TaskScheduler apiScheduler;
20+
private final ApiScheduler apiScheduler;
21+
@Value("${bot-handler.fixed-rate}")
22+
private final Duration fixedRate;
23+
24+
protected SchedulerConfig(ApiScheduler apiScheduler, @Value("${bot-handler.fixed-rate}") Duration fixedRate) {
25+
this.apiScheduler = apiScheduler;
26+
this.fixedRate = fixedRate;
27+
}
28+
29+
@Override
30+
public void configureTasks(ScheduledTaskRegistrar registrar) {
31+
// registrar.setScheduler(apiScheduler); //멀티 쓰레드 x
32+
registrar.addFixedRateTask(() -> {
33+
try {
34+
apiScheduler.MarketAllRequest();
35+
} catch (InterruptedException e) {
36+
throw new RuntimeException(e);
37+
}
38+
}, fixedRate);
39+
}
40+
}
Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
package com.cleanengine.coin.realitybot.controller;
22

3-
import com.cleanengine.coin.realitybot.api.BithumbAPIClient;
4-
import org.springframework.web.bind.annotation.GetMapping;
53
import org.springframework.web.bind.annotation.RequestMapping;
64
import org.springframework.web.bind.annotation.RestController;
75

86
@RestController
97
@RequestMapping("/api")
108
public class ApiController {
11-
private final BithumbAPIClient bithumbAPIClient;
12-
public ApiController(BithumbAPIClient bithumbAPIClient) {
13-
this.bithumbAPIClient = bithumbAPIClient;
14-
}
15-
@GetMapping("/test")
16-
public String getApiData(){
17-
// return bithumbAPIClient.get(ticekr);
18-
return null;
19-
20-
}}
9+
//초기 api 작동 확인용 -> realitybot controller로 전환 필요
10+
// private final BithumbAPIClient bithumbAPIClient;
11+
// public ApiController(BithumbAPIClient bithumbAPIClient) {
12+
// this.bithumbAPIClient = bithumbAPIClient;
13+
// }
14+
// @GetMapping("/test/{tickerName}")
15+
// public String getApiData(@PathVariable String tickerName) {
16+
// System.out.println("tickername 출력"+tickerName);
17+
// return bithumbAPIClient.get(tickerName);
18+
// }
19+
}

0 commit comments

Comments
 (0)