Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.cleanengine.coin.realitybot.api;

import com.cleanengine.coin.common.annotation.WorkingServerProfile;
import com.cleanengine.coin.order.adapter.out.persistentce.asset.AssetRepository;
import com.cleanengine.coin.order.domain.Asset;
import com.cleanengine.coin.realitybot.domain.APIVWAPState;
import com.cleanengine.coin.realitybot.domain.VWAPMetricsRecorder;
import com.cleanengine.coin.realitybot.dto.Ticks;
import com.cleanengine.coin.realitybot.parser.TickParser;
import com.cleanengine.coin.realitybot.service.OrderGenerateService;
import com.cleanengine.coin.realitybot.service.PlatformVWAPService;
import com.cleanengine.coin.realitybot.service.TickServiceManager;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
Expand All @@ -33,8 +33,9 @@ public class ApiScheduler {
private final CoinoneAPIClient coinoneAPIClient;
private final VWAPMetricsRecorder recorder;
private final MeterRegistry meterRegistry;
private final PlatformVWAPService platformVWAPService;

// @Scheduled(fixedRate = 5000)
// @Scheduled(fixedRate = 5000)
public void MarketAllRequest() throws InterruptedException {
Timer timer = meterRegistry.timer("apischeduler.request.duration");
timer.record(() -> {
Expand All @@ -53,24 +54,37 @@ public void MarketDataRequest(String ticker){

APIVWAPState apiVWAPState = tickServiceManager.getService(ticker);
long lastSeqId = lastSequentialIdMap.getOrDefault(ticker,0L);
boolean newTickAdded = false;

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

newTickAdded = true;
}
}
lastSequentialIdMap.put(ticker,lastSeqId);

double vwap = apiVWAPState.getVWAP();
double volume = apiVWAPState.getAvgVolumePerOrder();
double platformVwap = platformVWAPService.calculateVWAPbyTrades(ticker,vwap);
double difference = Math.abs((platformVwap - vwap)/vwap);
recorder.recordApiVwap(ticker,vwap);

orderGenerateService.generateOrder(ticker,vwap,volume); //1tick 당 매수/매도 3개씩 제작
if (difference>0.001){
orderGenerateService.generateOrder(ticker,vwap,volume); //1tick 당 매수/매도 3개씩 제작
// log.info("기준치 초과시 {}의 가격 : {} , 볼륨 : {}, 오차 : {}",ticker, vwap, volume, difference);
} else {
if (newTickAdded){
orderGenerateService.generateOrder(ticker,vwap,volume);
// log.info("기준치 이내 {}의 가격 : {} , 볼륨 : {}, 오차 : {}",ticker, vwap, volume, difference);
}
}



// log.info("작동확인 {}의 가격 : {} , 볼륨 : {}",ticker, vwap, volume);
}

/* @Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void addTick(Ticks tick){

//n초마다 5회 주문 , api 체결 내역에서 10종목씩 비교
public double getAvgVolumePerOrder() {
return calculator.getTotalVolume() / 50;
return calculator.getTotalVolume() / ticksQueue.size();
}//todo 에러 인젝션으로 50일때와 5일때 복귀 속도 알아보기

public double getVWAP(){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,20 @@ public void generateOrder(String ticker, double apiVWAP, double avgVolume) {//
recorder.recordPlatformVwap(ticker,platformVWAP);
//편차 계산 (vwap 기준)
double trendLineRate = (platformVWAP - apiVWAP)/ apiVWAP;


for(int level : orderLevels) { //1주문당 3회 매수매도 처리
OrderPricePolicy.OrderPrice basePrice = orderPricePolicy.calculatePrice(level,platformVWAP, unitPrice,trendLineRate);
DeviationPricePolicy.AdjustPrice adjustPrice = deviationPricePolicy.adjust(
basePrice.sell(), basePrice.buy(), trendLineRate, apiVWAP, unitPrice);
OrderPricePolicy.OrderPrice basePrice = orderPricePolicy.calculatePrice(level,apiVWAP,platformVWAP, unitPrice);
// DeviationPricePolicy.AdjustPrice adjustPrice = deviationPricePolicy.adjust(basePrice.sell(), basePrice.buy(), trendLineRate, apiVWAP, unitPrice);

double sellVolume = orderVolumePolicy.calculateVolume(avgVolume,trendLineRate,false);
double buyVolume = orderVolumePolicy.calculateVolume(avgVolume,trendLineRate,true);
double sellPrice = adjustPrice.sell();
double buyPrice = adjustPrice.buy();
// double sellPrice = adjustPrice.sell();
// double buyPrice = adjustPrice.buy();


createOrderWithFallback(ticker,false, sellVolume, sellPrice);
createOrderWithFallback(ticker,true, buyVolume, buyPrice);
createOrderWithFallback(ticker,false, sellVolume, basePrice.sell());
createOrderWithFallback(ticker,true, buyVolume, basePrice.buy());

/* DecimalFormat df = new DecimalFormat("#,##0.00");
DecimalFormat dfv = new DecimalFormat("#,###.########");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,16 @@ public class DeviationPricePolicy {

public AdjustPrice adjust(double platformSell,double platformBuy, double trendLineRate, double apiVWAP, double unitPrice){
double deviation = Math.abs(trendLineRate);//음수값 보정
if (deviation <= 0.017){
return new AdjustPrice(platformSell,platformBuy);
// 1% 이내 편차는 조정 없이 원본 가격 반환
if (deviation <= 0.001) { // 0.017 → 0.01로 변경
return new AdjustPrice(platformSell, platformBuy);
}

// 1% 초과 시에만 조정 로직 실행
double weight = getCorrectionWeight(deviation);
// double closeness = 1-weight; // 보간 가중치: 0.7 ~ 1.0 -> 0.5
double closeness; // 보간 가중치: 0.7 ~ 1.0 -> 0.5
if (deviation > 0.07){
closeness = Math.max(0.2, 1 - weight);
} else {
closeness = 0.01;
}
double closeness = (deviation > 0.07)
? Math.max(0.2, 1 - weight)
: 0.01;

// double targetVWAP = (trendLineRate > 0) //만약 closeness 를 0.5 입력시 중간값
// ? apiVWAP + (platformSell - apiVWAP) * closeness // 고평가 → platformSell(25000) → apiVWAP(16000) 사이 가중치 %로 유도
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,38 @@ public class OrderPricePolicy {
/**
* 레벨에 따라 매수/매도 가격을 계산합니다.
* @param level 주문 강도 (1~5)
* @param platformVWAP 플랫폼 기준 평균 체결 가격
// * @param platformVWAP 플랫폼 기준 평균 체결 가격
* @param unitPrice 호가 단위
* @param trendLineRate 플랫폼과 API VWAP의 편차율
// * @param trendLineRate 플랫폼과 API VWAP의 편차율
* @return PricePair (매도/매수 가격)
*/
public OrderPrice calculatePrice(int level,
double platformVWAP,
double unitPrice,
double trendLineRate) {
double priceOffset = unitPrice * level;
double sellPrice, buyPrice;
double randomOffset = Math.abs(getRandomOffset(platformVWAP,getDynamicMaxRate(trendLineRate)));
double basePrice = normalizeToUnit(platformVWAP, unitPrice); //기준 가격 (호가 단위 정규화)
public OrderPrice calculatePrice(int level, double apiVWAP, double platformVWAP, double unitPrice) {
double basePrice = normalizeToUnit(apiVWAP, unitPrice);
double targetPrice = normalizeToUnit(platformVWAP, unitPrice);

// 🔥 점진적 접근: API VWAP에서 Platform VWAP로 20% 씩 이동
double convergenceRate = 0.2; // 천천히 접근
double adjustedBase = basePrice + (targetPrice - basePrice) * convergenceRate;

if (level == 1){ //1level일 경우 주문이 겹치도록 설정
//체결을 위해 매수가 올리고, 매도가 내리는 계산 적용
sellPrice = normalizeToUnit(basePrice - randomOffset,unitPrice);
buyPrice = normalizeToUnit(basePrice + randomOffset,unitPrice);
}
//2~3 단계 : orderbook 단위 주문
else {
randomOffset = getRandomOffset(platformVWAP,0.001);
//체결 확률 증가용 코드
sellPrice = normalizeToUnit(platformVWAP + priceOffset - randomOffset,unitPrice);
buyPrice = normalizeToUnit(platformVWAP - priceOffset + randomOffset,unitPrice);
//안정적인 스프레드 유지
// sellPrice = normalizeToUnit(platformVWAP + priceOffset);
// buyPrice = normalizeToUnit(platformVWAP - priceOffset);
double priceOffset = unitPrice * level;

if (level == 1) {
// 1레벨: 조정된 기준가 근처 체결 유도
return new OrderPrice(adjustedBase + priceOffset/2, adjustedBase - priceOffset/2);
} else {
// 2~5레벨: 조정된 기준가 기준 스프레드
return new OrderPrice(adjustedBase + priceOffset, adjustedBase - priceOffset);
}
return new OrderPrice(sellPrice, buyPrice);
}

private double getRandomOffset(double basePrice, double maxRate){
//시장가에 해당하는 호가는 거래 체결 강하게 하기 위함
double percent = (Math.random() * 2-1)*maxRate;
private double getRandomOffset(double basePrice, double maxRate) {
double percent = (Math.random() * 2 - 1) * maxRate;
return basePrice * percent;
}

private double getDynamicMaxRate(double trendLineRate) {
// 편차가 벌어지면 벌어질수록 보정폭 확대
// 5% = 2.51의 가중치
// 11% = 5.51의 가중치
return 0.01 + Math.abs(trendLineRate) * 0.5;
}

private int normalizeToUnit(double price, double unitPrice){ //호가단위로 변환
return (int) ((double)(Math.round(price / unitPrice)) * unitPrice);
private double normalizeToUnit(double price, double unitPrice) {
return Math.round(price / unitPrice) * unitPrice;
}

public record OrderPrice(double sell, double buy){}
}
public record OrderPrice(double sell, double buy) {}
}
4 changes: 2 additions & 2 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,6 @@ server:
forward-headers-strategy: native

bot-handler:
fixed-rate: 5000 # 5초마다 실행
fixed-rate: 1000 # 5초마다 실행
cron : "0 0 0 * * *" # 매일 자정마다 호가
order-level : 1,2,3,4,5 #오더북 단계 설정 - 주문량 증가
order-level : 1,2 #오더북 단계 설정 - 주문량 증가
Loading