Skip to content
Closed
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
9 changes: 9 additions & 0 deletions skills/kuns9/trading-upbit-skill/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Upbit API Keys
UPBIT_OPEN_API_ACCESS_KEY=your_access_key_here
UPBIT_OPEN_API_SECRET_KEY=your_secret_key_here

# Bot Settings
PRICE_CHECK_INTERVAL=10000
TARGET_PROFIT=0.05
STOP_LOSS=-0.05
AUTO_TRADE=false
68 changes: 68 additions & 0 deletions skills/kuns9/trading-upbit-skill/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Upbit Trading Engine 🚀

안정성과 확장성을 최우선으로 설계된 업비트 자동 매매 엔진입니다. SKILLS 규격을 준수하여 체계적으로 코드를 관리합니다.

## 주요 특징

- 🏗️ **SKILLS 구조**: `scripts/`(로직), `resources/`(데이터), `backup/`(백업)으로 체기적 관리
- 🛡️ **견고한 예외 처리**: `upbitClient.js`를 통한 중앙 집중식 에러 관리 및 **429(Rate Limit) 자동 재시도**
- 🔍 **다이나믹 스케너**: 업비트의 모든 KRW 마켓을 실시간으로 자동 탐색하여 기회 포착
- 🚀 **이중 확인 전략**: 1일봉 돌파 신호를 60분봉 추세로 검증하는 Multi-Timeframe 분석
- 🔄 **상태 머신**: `FLAT` -> `ENTRY_PENDING` -> `OPEN` -> `EXIT_PENDING` -> `CLOSED` 상태 관리

## 시작하기

### 설치

```bash
# 저장소 복제
git clone <repository-url>
cd trading-upbit-skill

# 의존성 설치
npm install
```

### 설정

1. [Upbit API 키 발급](https://upbit.com/mypage/open_api_management)
2. `.env` 파일 설정:
```bash
cp .env.example .env
# .env 파일을 열어 발급받은 Access Key와 Secret Key를 입력하세요.
```

### 실행

시스템은 모니터와 이벤트 워커 두 가지 프로세스로 구성됩니다. 별도의 터미널에서 각각 실행해 주세요.

```bash
# 마켓 스캔 및 포지션 감시
node scripts/workers/monitor.js

# 이벤트 처리 및 매매 실행
node scripts/workers/eventWorker.js
```

## 디렉토리 구조

- `scripts/`: 핵심 실행 로직 및 모듈
- `workers/`: 배경에서 동작하는 작업자 프로세스
- `execution/`: 매매 실행 엔진
- `risk/`: 리스크 관리 필터
- `state/`: 포지션 및 상태 관리
- `resources/`: `positions.json`, `events.json` 등 동적 데이터 저장소
- `backup/`: 이전 코드 및 로그 백업
- `examples/`: 참고용 예제 코드

## Core Scripts

- `scripts/workers/monitor.js`: 동적 마켓 스캔 및 포지션 감시
- `scripts/workers/eventWorker.js`: 이벤트 전파 및 처리 프로세서
- `scripts/execution/tradeExecutor.js`: 상태 전이 기반 매매 엔진
- `scripts/risk/riskManager.js`: 리스크 평가 및 사이징 필터
- `scripts/state/positionsRepo.js`: 상태 머신 영속성 관리

## 라이선스

이 프로젝트는 ISC 라이선스를 따릅니다.
61 changes: 61 additions & 0 deletions skills/kuns9/trading-upbit-skill/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
name: Upbit Trading Engine
description: 안정성과 확장성을 최우선으로 설계된 업비트 자동 매매 엔진 (SKILLS 규격 준수)
metadata:
clawdbot:
requires:
env:
- UPBIT_OPEN_API_ACCESS_KEY
- UPBIT_OPEN_API_SECRET_KEY
- PRICE_CHECK_INTERVAL
- TARGET_PROFIT
- STOP_LOSS
- AUTO_TRADE
files:
- "skill.js"
- "scripts/**"
- "resources/**"
homepage: "https://github.com/sgyeo/trading-upbit-skill"
---

# Upbit Trading Engine 🚀

안정성과 확장성을 최우선으로 설계된 업비트 자동 매매 엔진입니다. (SKILLS 규격 준수)

## Key Features

- 🏗️ **SKILLS 구조**: `scripts/`(로직), `resources/`(데이터), `backup/`(백업)으로 체계적 관리
- 🛡️ **견고한 예외 처리**: `upbitClient.js`를 통한 중앙 집중식 에러 관리 및 **429(Rate Limit) 자동 재시도**
- 🔍 **다이나믹 스케너**: 업비트의 모든 KRW 마켓을 실시간으로 자동 탐색하여 기회 포착
- 🚀 **이중 확인 전략**: 1일봉 돌파 신호를 60분봉 추세로 검증하는 Multi-Timeframe 분석
- 🔄 **상태 머신**: `FLAT` -> `ENTRY_PENDING` -> `OPEN` -> `EXIT_PENDING` -> `CLOSED` 상태 관리

## Setup

1. [Upbit API 키 발급](https://upbit.com/mypage/open_api_management)
2. 환경변수 설정:
```bash
cp .env.example .env
```

3. 실행:
```bash
# 별도의 터미널에서 각각 실행
node scripts/workers/monitor.js
node scripts/workers/eventWorker.js
```

## Directory Structure

- `scripts/`: 실행 로직 및 핵심 모듈
- `resources/`: `positions.json`, `events.json` 등 동적 데이터
- `backup/`: 이전 코드 및 로그 백업
- `examples/`: 참고용 예제 및 설정

## Core Scripts

- `scripts/workers/monitor.js`: 동적 마켓 스캔 및 포지션 감시
- `scripts/workers/eventWorker.js`: 이벤트 전파 및 처리 프로세서
- `scripts/execution/tradeExecutor.js`: 상태 전이 기반 매매 엔진
- `scripts/risk/riskManager.js`: 리스크 평가 및 사이징 필터
- `scripts/state/positionsRepo.js`: 상태 머신 영속성 관리
19 changes: 19 additions & 0 deletions skills/kuns9/trading-upbit-skill/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "trading-upbit-skill",
"version": "1.0.0",
"description": "",
"main": "balance.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"axios": "^1.13.5",
"dotenv": "^17.3.1",
"jsonwebtoken": "^9.0.3",
"uuid": "^13.0.0"
}
}
82 changes: 82 additions & 0 deletions skills/kuns9/trading-upbit-skill/scripts/data/marketData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const { request } = require('./api-client');

/**
* 업비트 공용 시세 조회 모듈 (market.js)
*/

const BASE_URL = 'https://api.upbit.com/v1';

/**
* 마켓 코드 조회 (전체 종목 리스트)
*/
async function getMarkets(filterKRW = true) {
const data = await request({
method: 'get',
url: `${BASE_URL}/market/all?isDetails=false`
});

return filterKRW ? data.filter(m => m.market.startsWith('KRW-')) : data;
}

/**
* 캔들 조회 (통합 함수)
*/
async function getCandles(unit, market, count = 1, subUnit = 1) {
let url = `${BASE_URL}/candles/${unit}`;
if (unit === 'minutes') {
url += `/${subUnit}`;
}

return request({
method: 'get',
url,
params: { market, count }
});
}

/**
* 현재가 정보 조회 (Ticker)
*/
async function getTickers(markets) {
const marketsStr = Array.isArray(markets) ? markets.join(',') : markets;
return request({
method: 'get',
url: `${BASE_URL}/ticker`,
params: { markets: marketsStr }
});
}

/**
* 호가 조회 (Orderbook)
*/
async function getOrderbooks(markets) {
const marketsStr = Array.isArray(markets) ? markets.join(',') : markets;
return request({
method: 'get',
url: `${BASE_URL}/orderbook`,
params: { markets: marketsStr }
});
}

module.exports = {
getMarkets,
getCandles,
getTickers,
getOrderbooks
};

// 테스트 코드
if (require.main === module) {
(async () => {
try {
console.log('--- Market Module Test ---');
const btcTicker = await getTickers('KRW-BTC');
console.log('BTC Ticker:', btcTicker[0].trade_price);

const krwMarkets = await getMarkets();
console.log('KRW Markets Count:', krwMarkets.length);
} catch (err) {
console.error('Test Failed:', err.message);
}
})();
}
69 changes: 69 additions & 0 deletions skills/kuns9/trading-upbit-skill/scripts/execution/orderService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* 주문 서비스 (orderService.js)
* - 시장가 매수(Price) / 시장가 매도(Market) 최적화
* - UpbitClient를 이용한 실제 API 호출
*/

const { Logger } = require('./upbitClient');

class OrderService {
constructor(upbitClient) {
this.client = upbitClient;
}

/**
* 시장가 매수 (Entry)
* @param {string} market - 마켓 코드
* @param {number} totalKRW - 총 주문 금액
*/
async placeMarketBuy(market, totalKRW) {
Logger.info(`[BUY] 시장가 매수 시도: ${market} - ${totalKRW.toLocaleString()} KRW`);

// 시장가 매수는 ord_type: 'price'
// volume은 없어야 하며 price가 총 KRW 금액이 됨
const orderData = {
market,
side: 'bid',
price: totalKRW.toString(),
ord_type: 'price'
};

return this.client.request('POST', '/orders', orderData);
}

/**
* 시장가 매도 (Exit)
* @param {string} market - 마켓 코드
* @param {number} volume - 매도 수량
*/
async placeMarketSell(market, volume) {
Logger.info(`[SELL] 시장가 매도 시도: ${market} - ${volume}`);

// 시장가 매도는 ord_type: 'market'
// price는 없어야 하며 volume이 수량이 됨
const orderData = {
market,
side: 'ask',
volume: volume.toString(),
ord_type: 'market'
};

return this.client.request('POST', '/orders', orderData);
}

/**
* 주문 조회
*/
async getOrder(uuid) {
return this.client.request('GET', '/order', {}, { uuid });
}

/**
* 주문 취소
*/
async cancelOrder(uuid) {
return this.client.request('DELETE', '/order', {}, { uuid });
}
}

module.exports = OrderService;
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* 통합 매매 실행자 (tradeExecutor.js)
* - 리스크 관리 확인
* - 주문 실행 및 상태 전이 (PositionsRepo 활용)
*/

const { Logger } = require('./upbitClient');
const riskManager = require('../risk/riskManager');
const positionsRepo = require('../state/positionsRepo');

class TradeExecutor {
constructor(orderService) {
this.orderService = orderService;
}

async execute(event) {
Logger.info(`[EXECUTOR] 이벤트 처리 시작: ${event.type} - ${event.market}`);

try {
// 1. 리스크 평가
const riskResult = await riskManager.evaluate(this.orderService.client, event);
if (!riskResult.allow) {
Logger.warn(`[SKIP] 리스크 필터에 의해 생략됨: ${riskResult.reason} (${riskResult.detail || ''})`);
return false;
}

// 2. 상태별 작업 및 전이
if (event.type === 'BUY_SIGNAL') {
// 이미 진행 중인 포지션이 있는지 확인 (중복 주문 방지)
const data = await positionsRepo.load();
if (data.positions.some(p => p.market === event.market && (p.state === 'OPEN' || p.state === 'ENTRY_PENDING'))) {
Logger.warn(`[SKIP] 이미 해당 마켓의 포지션이 존재하여 생략함: ${event.market}`);
return false;
}

// 상태 전이: FLAT -> ENTRY_PENDING
await positionsRepo.createEntryPending(event.market, event.meta?.strategy || 'unknown', riskResult.budgetKRW);

// 실제 주문 실행 (시장가 매수)
const orderResult = await this.orderService.placeMarketBuy(event.market, riskResult.budgetKRW);
Logger.info(`[DONE] 시장가 매수 주문 완료: ${orderResult.uuid}`);

// 상태 전이: ENTRY_PENDING -> OPEN (실제 체결 확인은 FillWatcher에서 보완)
await positionsRepo.updateToOpen(event.market, orderResult);
return true;
}

if (event.type === 'TARGET_HIT' || event.type === 'STOPLOSS_HIT') {
// 상태 전이: OPEN -> EXIT_PENDING
await positionsRepo.updateToExitPending(event.market, event.type);

// 실제 주문 실행 (시장가 매도)
const orderResult = await this.orderService.placeMarketSell(event.market, riskResult.volume);
Logger.info(`[DONE] 시장가 매도 주문 완료: ${orderResult.uuid}`);

// 상태 전이: EXIT_PENDING -> CLOSED
await positionsRepo.updateToClosed(event.market, orderResult);
return true;
}

return false;
} catch (err) {
Logger.error(`Trade Execution Error: ${err.message}`);
throw err;
}
}
}

module.exports = TradeExecutor;
Loading