Showing
9 changed files
with
154 additions
and
28 deletions
LICENSE
0 → 100644
| 1 | +Copyright (c) [2021] 김다빈,이가원 | ||
| 2 | + | ||
| 3 | +Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| 4 | +of this software and associated documentation files (the "Software"), to deal | ||
| 5 | +in the Software without restriction, including without limitation the rights | ||
| 6 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| 7 | +copies of the Software, and to permit persons to whom the Software is | ||
| 8 | +furnished to do so, subject to the following conditions: | ||
| 9 | + | ||
| 10 | +The above copyright notice and this permission notice shall be included in all | ||
| 11 | +copies or substantial portions of the Software. | ||
| 12 | + | ||
| 13 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 14 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 15 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 16 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 17 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 18 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 19 | +SOFTWARE. | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | -#Trading Service | 1 | +<div align="center"> |
| 2 | + <a href="https://github.com/othneildrew/Best-README-Template"> | ||
| 3 | + <img src="images/logo.png" alt="Logo" width="80" height="80"> | ||
| 4 | + </a> | ||
| 2 | 5 | ||
| 3 | -./config/dev.js 생성 필요 | 6 | + <h3 align="center">업비트 암호화폐 자동 매매 서비스</h3> |
| 4 | -module.exports = { | 7 | + </p> |
| 5 | - mongoURI:{mongoURI} | 8 | +</div> |
| 6 | -} | 9 | + |
| 7 | -.env 파일 생성 후 access_key와 secret_key 할당 필요 | 10 | +## About The Project |
| 11 | +<img src="images/about01.jpg" alt="about" width="80" height="80"> | ||
| 12 | +<h4>24시간 시세가 변하는 암호화폐를 위한 자동 매매 서비스<br/></h4> | ||
| 13 | +해당 서비스를 만든 이유 <br/> | ||
| 14 | +● 24시간 가격변동이 있지만 사람이 하루종일 보고 있을 수는 없다.<br/> | ||
| 15 | +● 매매 기준을 정해두어도 이성적인 판단을 못하고 감정적으로 매매를 할 수 있다.<br/> | ||
| 16 | +● 일상샐활을 하면서 고정적인 수익을 얻을 수 있다.<br/> | ||
| 17 | + | ||
| 18 | +#### DB Schema | ||
| 19 | +* User | ||
| 20 | + - uid : 유저의 고유 아이디 | ||
| 21 | + - krw_balance : 보유 원화 | ||
| 22 | + - market : 보유 암호화폐 이름 | ||
| 23 | + - count : 매수 횟수 | ||
| 24 | + - avg_buy_price : 매수 평균가 | ||
| 25 | + - volume : 보유 암호화폐 갯수 | ||
| 26 | + | ||
| 27 | +* Coin | ||
| 28 | + - tid : 거래량별 암호화폐 순위 | ||
| 29 | + - name : 암호화폐 이름 | ||
| 30 | + - korean_name : 암호화폐 한글 이름 | ||
| 31 | + - acc_trade_price_24h : 24시간 거래대금 | ||
| 32 | + - current_price : 현재 암호화폐 가격 | ||
| 33 | + - up_count : 5분 간격 연속 상승 횟수 | ||
| 34 | + - down_count : 5분 간격 연속 하락 횟수 | ||
| 35 | + | ||
| 36 | +### Built With | ||
| 37 | +* [Node.js](https://nodejs.org/) | ||
| 38 | + | ||
| 39 | +### Installation | ||
| 40 | +1. repository를 clone한다. | ||
| 41 | +```sh | ||
| 42 | + git clone http://khuhub.khu.ac.kr/Crypto/Crypto_trade_bot.git | ||
| 43 | +``` | ||
| 44 | +2. NPM package들을 설치한다. | ||
| 45 | +```sh | ||
| 46 | + npm install | ||
| 47 | +``` | ||
| 48 | +3. 해당 프로젝트는 aws ec2와 ssl인증서를 사용한다. 개인 PC에서 작동하려면 domain과 sslport를 지우고 express.app을 사용해서 서버를 작동시키면 된다.(이 경우 4번은 생략) | ||
| 49 | +4. 최상위 폴더의 index.js의 domain과 sslport를 본인 domain과 sslport로 변경한다. | ||
| 50 | +```js | ||
| 51 | +const domain = "{your domain}"; | ||
| 52 | +const sslport = {your port}; | ||
| 53 | +``` | ||
| 54 | +5. https://cloud.mongodb.com/ 가입 후 db connect url을 복사한다. | ||
| 55 | +6. /config 폴더에 dev.js 파일 추가 | ||
| 56 | +```js | ||
| 57 | + module.exports = { | ||
| 58 | + mongoURI:"복사한 connect url" | ||
| 59 | + } | ||
| 60 | +``` | ||
| 61 | +7. https://upbit.com/service_center/open_api_guide 에서 access_key와 secret_key를 발급받는다. | ||
| 62 | +8. https://developers.line.biz/en/services/messaging-api/ 에서 line message-api를 생성하고 channel access token을 복사한다. | ||
| 63 | +9. 3번에서 입력한 도메인을 webhook url로 설정해준다.(4번을 생략했다면 webhook url에 본인 네트워크망IP를 입력) | ||
| 64 | +10. 프로젝트 최상위 폴더에 .env 파일 추가 | ||
| 65 | +```env | ||
| 66 | + access_key={upbit에서 발급 받은 access_key} | ||
| 67 | + secret_key={upbit에서 발급 받은 secret_key} | ||
| 68 | + token={line message-api 의 channel access token} | ||
| 69 | + userid={테스트 알림을 받을 line userid} | ||
| 70 | +``` | ||
| 71 | +## Usage | ||
| 72 | +프로젝트 최상위 폴더에서 명령어 실행 | ||
| 73 | +```sh | ||
| 74 | + nodemon | ||
| 75 | +``` | ||
| 76 | +<img src="images/first.jpg" alt="first" width="80" height="80"> | ||
| 77 | +<img src="images/second.jpg" alt="second" width="80" height="80"> | ||
| 78 | + | ||
| 79 | +## Roadmap | ||
| 80 | +- [x] Add get_marketname | ||
| 81 | +- [x] Add get_marketInfo | ||
| 82 | +- [x] Add price_comparison | ||
| 83 | +- [x] Add transaction_coin | ||
| 84 | +- [x] Add latest_repeat | ||
| 85 | +- [x] Add send the order breakdown to the user | ||
| 86 | +- [x] Add send the asset to the user | ||
| 87 | +- [x] Add Whenever a sale is signed, the details of the signing are sent. | ||
| 88 | + | ||
| 89 | +## Contributing | ||
| 90 | + | ||
| 91 | +1. 프로젝트를 fork 하세요. | ||
| 92 | +2. 여러분의 feature 브랜치를 만들어 주세요.(`git checkout -b feature/{function}`) | ||
| 93 | +3. 변경사항을 commit 해주세요.(`git commit -m 'Add some function`) | ||
| 94 | +4. 원격 브랜치로 push 해주세요.(`git push origin feature/{function}`) | ||
| 95 | +5. pull request를 보내주세요. | ||
| 96 | + | ||
| 97 | +## License | ||
| 98 | +[MIT License](http://khuhub.khu.ac.kr/Crypto/Crypto_trade_bot/blob/master/LICENSE) | ||
| 99 | + | ||
| 100 | +<!-- CONTACT --> | ||
| 101 | +## Contact | ||
| 102 | + | ||
| 103 | +이름 - [@김다빈,이가원](https://github.com/dogsoft0937) - dogsoft0937@khu.ac.kr,juneee0864@gmail.com <br/> | ||
| 104 | +프로젝트 주소: [http://khuhub.khu.ac.kr/Crypto/Crypto_trade_bot](http://khuhub.khu.ac.kr/Crypto/Crypto_trade_bot) | ||
| 105 | +## Acknowledgments | ||
| 106 | + | ||
| 107 | +* [upbit-reference](https://docs.upbit.com/reference) | ||
| 108 | +* [line-reference](https://developers.line.biz/en/reference/messaging-api/) | ... | ... |
images/about01.jpg
0 → 100644
141 KB
images/first.PNG
0 → 100644
11.6 KB
images/logo.jpg
0 → 100644
11.6 KB
images/second.PNG
0 → 100644
28.2 KB
| ... | @@ -12,8 +12,7 @@ const HTTPS = require('https'); | ... | @@ -12,8 +12,7 @@ const HTTPS = require('https'); |
| 12 | const domain = "2019102152.osschatbot.ga" | 12 | const domain = "2019102152.osschatbot.ga" |
| 13 | const sslport = 23023; | 13 | const sslport = 23023; |
| 14 | const TARGET_URL = 'https://api.line.me/v2/bot/message/reply' | 14 | const TARGET_URL = 'https://api.line.me/v2/bot/message/reply' |
| 15 | -const TOKEN = process.env.token; | 15 | + |
| 16 | -var userid = process.env.userid; | ||
| 17 | 16 | ||
| 18 | const bodyParser = require('body-parser'); | 17 | const bodyParser = require('body-parser'); |
| 19 | const crypto = require('crypto'); | 18 | const crypto = require('crypto'); |
| ... | @@ -24,6 +23,8 @@ const sign = require('jsonwebtoken').sign | ... | @@ -24,6 +23,8 @@ const sign = require('jsonwebtoken').sign |
| 24 | const access_key = process.env.access_key; | 23 | const access_key = process.env.access_key; |
| 25 | const secret_key = process.env.secret_key; | 24 | const secret_key = process.env.secret_key; |
| 26 | const server_url = "https://api.upbit.com" | 25 | const server_url = "https://api.upbit.com" |
| 26 | +const TOKEN = process.env.token; | ||
| 27 | +const userid = process.env.userid; | ||
| 27 | 28 | ||
| 28 | var delay_count=1; | 29 | var delay_count=1; |
| 29 | 30 | ||
| ... | @@ -76,7 +77,7 @@ function get_asset(market) { | ... | @@ -76,7 +77,7 @@ function get_asset(market) { |
| 76 | // hold_coin[data[i].currency]={avg_buy_price:data[i].avg_buy_price,volume:data[i].balance,current_price:candle[0].trade_price}; | 77 | // hold_coin[data[i].currency]={avg_buy_price:data[i].avg_buy_price,volume:data[i].balance,current_price:candle[0].trade_price}; |
| 77 | } | 78 | } |
| 78 | } | 79 | } |
| 79 | - resolve({ pre_asset: pre_asset.toFixed(0), current_asset: current_asset.toFixed(0), profit_data: (current_asset - pre_asset).toFixed(0), yield_data: ((current_asset - pre_asset) / pre_asset * 100).toFixed(2) }); | 80 | + resolve({ pre_asset: pre_asset.toFixed(0).replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ","), current_asset: current_asset.toFixed(0).replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ","), profit_data: (current_asset - pre_asset).toFixed(0).replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ",") , yield_data: Number((current_asset - pre_asset) / pre_asset * 100).toFixed(2).replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ",") }); |
| 80 | } else if (market == "coin") { | 81 | } else if (market == "coin") { |
| 81 | var hold_coin = new Object(); | 82 | var hold_coin = new Object(); |
| 82 | 83 | ||
| ... | @@ -132,9 +133,14 @@ async function get_marketName() { | ... | @@ -132,9 +133,14 @@ async function get_marketName() { |
| 132 | } | 133 | } |
| 133 | async function transaction_coin(coin_name, side, volume, price, ord_type, first = true) { | 134 | async function transaction_coin(coin_name, side, volume, price, ord_type, first = true) { |
| 134 | var volume = volume; | 135 | var volume = volume; |
| 135 | - if (side == "ask" && volume == "") { | 136 | + var avg_buy_price=0,market=""; |
| 137 | + if (side == "ask") { | ||
| 136 | await User.findOne({ market: coin_name }).then((result) => { | 138 | await User.findOne({ market: coin_name }).then((result) => { |
| 137 | - volume = String(result.volume); | 139 | + if(volume == ""){ |
| 140 | + volume = String(result.volume); | ||
| 141 | + } | ||
| 142 | + avg_buy_price=result.avg_buy_price; | ||
| 143 | + market=result.market.split('-')[1]; | ||
| 138 | }).catch(err => { console.log(err.error) }); | 144 | }).catch(err => { console.log(err.error) }); |
| 139 | } | 145 | } |
| 140 | const body = { | 146 | const body = { |
| ... | @@ -144,7 +150,6 @@ async function transaction_coin(coin_name, side, volume, price, ord_type, first | ... | @@ -144,7 +150,6 @@ async function transaction_coin(coin_name, side, volume, price, ord_type, first |
| 144 | price: price, | 150 | price: price, |
| 145 | ord_type: ord_type, | 151 | ord_type: ord_type, |
| 146 | } | 152 | } |
| 147 | - console.log(body); | ||
| 148 | //시장가 매수인 경우 price를 얼마치 살건지 입력 | 153 | //시장가 매수인 경우 price를 얼마치 살건지 입력 |
| 149 | //시장가 매도인경우 volume에 몇개를 팔건지 입력 | 154 | //시장가 매도인경우 volume에 몇개를 팔건지 입력 |
| 150 | const query = queryEncode(body) | 155 | const query = queryEncode(body) |
| ... | @@ -170,8 +175,10 @@ async function transaction_coin(coin_name, side, volume, price, ord_type, first | ... | @@ -170,8 +175,10 @@ async function transaction_coin(coin_name, side, volume, price, ord_type, first |
| 170 | }, delay) | 175 | }, delay) |
| 171 | }) | 176 | }) |
| 172 | var my_asset = await asset(1000); | 177 | var my_asset = await asset(1000); |
| 173 | - if(my_asset.market&&my_asset.market.avg_buy_price){ | 178 | + |
| 174 | - lookup_order(result.uuid,my_asset.market.avg_buy_price); | 179 | + if(avg_buy_price!=0){ |
| 180 | + console.log(avg_buy_price); | ||
| 181 | + lookup_order(result.uuid,avg_buy_price,market); | ||
| 175 | }else{ | 182 | }else{ |
| 176 | lookup_order(result.uuid); | 183 | lookup_order(result.uuid); |
| 177 | } | 184 | } |
| ... | @@ -332,7 +339,6 @@ async function price_comparison(candle, user_data = null, isbuying = false) { | ... | @@ -332,7 +339,6 @@ async function price_comparison(candle, user_data = null, isbuying = false) { |
| 332 | }).catch(err => { console.log(err); }) | 339 | }).catch(err => { console.log(err); }) |
| 333 | } | 340 | } |
| 334 | }else{ | 341 | }else{ |
| 335 | - console.log(촛불); | ||
| 336 | console.log(result); | 342 | console.log(result); |
| 337 | } | 343 | } |
| 338 | }) | 344 | }) |
| ... | @@ -371,7 +377,7 @@ async function latest_repeat(t1) { | ... | @@ -371,7 +377,7 @@ async function latest_repeat(t1) { |
| 371 | let today = new Date(); | 377 | let today = new Date(); |
| 372 | let minutes = today.getMinutes(); | 378 | let minutes = today.getMinutes(); |
| 373 | let seconds = today.getSeconds(); | 379 | let seconds = today.getSeconds(); |
| 374 | - if (seconds == 0) { | 380 | + if (seconds ==0) { |
| 375 | // if (minutes == 0 && seconds == 0) { | 381 | // if (minutes == 0 && seconds == 0) { |
| 376 | clearInterval(check_time); | 382 | clearInterval(check_time); |
| 377 | sort_info = (await sort_data()); | 383 | sort_info = (await sort_data()); |
| ... | @@ -401,7 +407,7 @@ async function latest_repeat(t1) { | ... | @@ -401,7 +407,7 @@ async function latest_repeat(t1) { |
| 401 | } | 407 | } |
| 402 | }, 1000); | 408 | }, 1000); |
| 403 | } | 409 | } |
| 404 | -function lookup_order(uuid,avg_buy_price=0) { | 410 | +function lookup_order(uuid,avg_buy_price=0,market="") { |
| 405 | const body = { | 411 | const body = { |
| 406 | uuid: uuid | 412 | uuid: uuid |
| 407 | } | 413 | } |
| ... | @@ -441,21 +447,21 @@ function lookup_order(uuid,avg_buy_price=0) { | ... | @@ -441,21 +447,21 @@ function lookup_order(uuid,avg_buy_price=0) { |
| 441 | volume = volume.toFixed(8).toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ","); | 447 | volume = volume.toFixed(8).toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ","); |
| 442 | funds = funds.toFixed(0).toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ","); | 448 | funds = funds.toFixed(0).toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ","); |
| 443 | if(avg_buy_price!=0){ | 449 | if(avg_buy_price!=0){ |
| 444 | - trading_notice(body.side, price, funds, volume,yield_data); | 450 | + trading_notice(body.side, price, funds, volume,yield_data,market); |
| 445 | }else{ | 451 | }else{ |
| 446 | trading_notice(body.side, price, funds, volume); | 452 | trading_notice(body.side, price, funds, volume); |
| 447 | } | 453 | } |
| 448 | }) | 454 | }) |
| 449 | return; | 455 | return; |
| 450 | } | 456 | } |
| 451 | -function trading_notice(side, price, funds, volume,yield_data=0) { | 457 | +function trading_notice(side, price, funds, volume,yield_data=0,market="") { |
| 452 | var messages = new Array(); | 458 | var messages = new Array(); |
| 453 | if (side == "bid") { | 459 | if (side == "bid") { |
| 454 | messages.push({ "type": "text", "text": "매수 주문이 체결되었습니다." }); | 460 | messages.push({ "type": "text", "text": "매수 주문이 체결되었습니다." }); |
| 455 | - messages.push({ "type": "text", "text": `체결 금액 : ${funds}원\n체결 수량 : ${volume}개\n체결 평균가 : ${price}원\n` }); | 461 | + messages.push({ "type": "text", "text": `${market}\n체결 금액 : ${funds}원\n체결 수량 : ${volume}개\n체결 평균가 : ${price}원\n` }); |
| 456 | } else { | 462 | } else { |
| 457 | messages.push({ "type": "text", "text": "매도 주문이 체결되었습니다." }); | 463 | messages.push({ "type": "text", "text": "매도 주문이 체결되었습니다." }); |
| 458 | - messages.push({ "type": "text", "text": `체결 금액 : ${funds}원\n체결 수량 : ${volume}개\n체결 평균가 : ${price}원\n수익률 : ${yield_data}%` }); | 464 | + messages.push({ "type": "text", "text": `${market}\n체결 금액 : ${funds}원\n체결 수량 : ${volume}개\n체결 평균가 : ${price}원\n수익률 : ${yield_data}%` }); |
| 459 | } | 465 | } |
| 460 | 466 | ||
| 461 | var TARGET_URL = 'https://api.line.me/v2/bot/message/push'; | 467 | var TARGET_URL = 'https://api.line.me/v2/bot/message/push'; | ... | ... |
| 1 | const mongoose=require('mongoose'); | 1 | const mongoose=require('mongoose'); |
| 2 | - | ||
| 3 | const coinSchema=mongoose.Schema({ | 2 | const coinSchema=mongoose.Schema({ |
| 4 | tid:{ | 3 | tid:{ |
| 5 | type:Number, | 4 | type:Number, |
| ... | @@ -20,11 +19,14 @@ const coinSchema=mongoose.Schema({ | ... | @@ -20,11 +19,14 @@ const coinSchema=mongoose.Schema({ |
| 20 | type:Number, | 19 | type:Number, |
| 21 | required:true | 20 | required:true |
| 22 | }, | 21 | }, |
| 23 | - count:{ | 22 | + up_count:{ |
| 23 | + type:Number, | ||
| 24 | + default:0 | ||
| 25 | + }, | ||
| 26 | + down_count:{ | ||
| 24 | type:Number, | 27 | type:Number, |
| 25 | default:0 | 28 | default:0 |
| 26 | } | 29 | } |
| 27 | }) | 30 | }) |
| 28 | - | ||
| 29 | const Coin=mongoose.model("Coin",coinSchema); | 31 | const Coin=mongoose.model("Coin",coinSchema); |
| 30 | module.exports={Coin}; | 32 | module.exports={Coin}; |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | const mongoose=require('mongoose'); | 1 | const mongoose=require('mongoose'); |
| 2 | - | ||
| 3 | const userSchema=mongoose.Schema({ | 2 | const userSchema=mongoose.Schema({ |
| 4 | uid:{ | 3 | uid:{ |
| 5 | type:Number | 4 | type:Number |
| ... | @@ -11,7 +10,7 @@ const userSchema=mongoose.Schema({ | ... | @@ -11,7 +10,7 @@ const userSchema=mongoose.Schema({ |
| 11 | type:String, | 10 | type:String, |
| 12 | }, | 11 | }, |
| 13 | count:{ | 12 | count:{ |
| 14 | - type:Number, | 13 | + type:Number |
| 15 | }, | 14 | }, |
| 16 | avg_buy_price:{ | 15 | avg_buy_price:{ |
| 17 | type:Number | 16 | type:Number |
| ... | @@ -20,6 +19,5 @@ const userSchema=mongoose.Schema({ | ... | @@ -20,6 +19,5 @@ const userSchema=mongoose.Schema({ |
| 20 | type:Number | 19 | type:Number |
| 21 | } | 20 | } |
| 22 | }) | 21 | }) |
| 23 | - | ||
| 24 | const User=mongoose.model("User",userSchema); | 22 | const User=mongoose.model("User",userSchema); |
| 25 | -module.exports={User}; | 23 | +module.exports={User}; |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
-
Please register or login to post a comment