일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 라즈베리파이
- Selenium
- 우분투 20.04
- 제주도
- codeigniter
- TiL
- C
- FMS
- Ubuntu 20.04
- Raspberry Pi
- MySQL
- 맛집
- ubuntu
- 옵티머스g
- 우분투
- 옵지
- 맥
- 프레임워크
- 셀레니움
- 라즈비안
- 옵티머스 g
- upbit
- 회고
- 코드이그나이터
- php
- 옵G
- Laravel
- 라라벨
- 20.04
- 업비트
- Today
- Total
평범한 이야기들
[PHP] PHP를 이용해 업비트(upbit) API 통신하기 #4 리뉴얼 본문
지난 3월에 만들었던 업비트(upbit)에서 제공하는 API를 이용해서 실시간으로 통신하는 프로그램을 만들었습니다. 그 후 잠시 손을 놓고 있었는데 최근에 다시 확인해 볼 일이 있어서 프로그램을 열어보았고 좀 더 쉽게 사용할 수 있도록 변경을 해보았습니다.
필요 패키지
업비트는 REST API 요청 시, 발급받은 access key와 secret key로 토큰을 생성하여 Authorization 헤더를 통해 전송합니다. 토큰은 JWT(https://jwt.io) 형식을 따릅니다. JWT 사이트에 가시면 다양한 언어와 버전, 그리고 설치하는 방법 등 들이 나와있으니 확인해보시면 됩니다. 저는 firebase/php-jwt 버전으로 설치했습니다. 설치는 composer를 이용했습니다.
composer require firebase/php-jwt
두번째로는 HTTP client 라이브러리 인 guzzlehttp/guzzle 라이브러리를 설치했습니다. 기존에 curl로 만들었던 부분을 psr-7, psr-18에 맞는 인터페이스를 PHP에서 권장하는 방법으로 수정을 했습니다. guzzlehttp/guzzle 은 쉽게 설치를 할 수 있고, 사용도 쉽게 할 수 있기 때문에 많은 곳에서 사용하고 있는 라이브러리입니다. 설치는 composer를 이용했습니다.
composer require guzzlehttp/guzzle
소스코드
소스는 기본적으로 캔들조회, 계좌조회, 주문, 주문 취소 등 가장 기본적인 기능만 추가해서 작성했습니다. 소스코드는 깃허브 (https://github.com/songsariya/upbit-php.git)에 등록되었습니다.
아래는 소스코드 전체 입니다.
<?php
use Exception;
use Firebase\JWT\JWT;
/**
* Upbit Api
* Upbit 서버와 통신을 하는 메소드로 구성
*/
class UpbitApi
{
const POST = "POST";
const GET = "GET";
const PUT = "PUT";
const DELETE = "DELETE";
const HTTP_CODE_200 = "200";
const SIDE_BID = "bid"; // 매수
const SIDE_ASK = "ask"; // 매도
const ORD_TYPE_LIMIT = "limit"; // 지정가 주문
const ORD_TYPE_PRICE = "price"; // 시장가 주문 (매수)
const ORD_TYPE_MARKET = "market"; // 시장가 주문 (매도)
private $accessKey;
private $secretKey;
private \GuzzleHttp\Client $client;
/**
* 생성자
*/
public function __construct(string $accessKey, string $secretKey)
{
$this->accessKey = $accessKey;
$this->secretKey = $secretKey;
$this->client = new \GuzzleHttp\Client(['base_uri' => 'https://api.upbit.com/v1/']);
}
/**
* 마켓 코드 조회
*
* @return array
*/
public function getMarketList(): array
{
$uri = "market/all?isDetails=false";
return $this->send(self::GET, $uri);
}
/**
* 분 Candle 정보 조회
*
* @param int $unit 분 단위 1 3 5 15 10 30 60 240 분 캔들 조회
* @param string $market 마켓 코드
* @param string|null $to 마지막 캔들 시각 포맷 (yyyy-MM-dd'T'HH:mm:ss'Z') 또는 (yyyy-MM-dd HH:mm:ss), 빈 값 요청시 가장 최근 캔들
* @param int|null $count 캔들 갯수(최대 200개)
* @return array
*/
public function getCandlesMinutes(int $unit, string $market, ?string $to = null, ?int $count = null): array
{
$uri = "candles/minutes/{$unit}?market={$market}";
$uri .= $to ? "&to={$to}" : "";
$uri .= $count ? "&count={$count}" : "";
return $this->send(self::GET, $uri);
}
/**
* 일 Candle 정보 조회
*
* @param string $market 마켓 코드
* @param integer|null $count 캔들 갯수(최대 200개)
* @param string|null $to 마지막 캔들 시각 포맷 (yyyy-MM-dd'T'HH:mm:ss'Z') 또는 (yyyy-MM-dd HH:mm:ss), 빈 값 요청시 가장 최근 캔들
* @param string|null $convertingPriceUnit 종가 환산 화폐 단위 (생략 가능 KRW로 명시할 시 원화 환산 가격을 반환)
* @return array
*/
public function getCandlesDays(string $market, ?int $count = null, ?string $to = null, ?string $convertingPriceUnit): array
{
$uri = "candles/days?market={$market}";
$uri .= $count ? "&count={$count}" : "";
$uri .= $to ? "&to={$to}" : "";
$uri .= $convertingPriceUnit ? "&convertingPriceUnit={$convertingPriceUnit}" : "";
return $this->send(self::GET, $uri);
}
/**
* 주 Candle 정보 조회
*
* @param string $market 마켓 코드
* @param integer|null $count 캔들 갯수(최대 200개)
* @param string|null $to 마지막 캔들 시각 포맷 (yyyy-MM-dd'T'HH:mm:ss'Z') 또는 (yyyy-MM-dd HH:mm:ss), 빈 값 요청시 가장 최근 캔들
* @return array
*/
public function getCandlesWeeks(string $market, ?int $count = null, ?string $to = null): array
{
$uri = "candles/weeks?market={$market}";
$uri .= $count ? "&count={$count}" : "";
$uri .= $to ? "&to={$to}" : "";
return $this->send(self::GET, $uri);
}
/**
* 월 Candle 정보 조회
*
* @param string $market 마켓 코드
* @param integer|null $count 캔들 갯수(최대 200개)
* @param string|null $to 마지막 캔들 시각 포맷 (yyyy-MM-dd'T'HH:mm:ss'Z') 또는 (yyyy-MM-dd HH:mm:ss), 빈 값 요청시 가장 최근 캔들
* @return array
*/
public function getCandlesMonths(string $market, ?int $count = null, ?string $to = null): array
{
$uri = "candles/months?market={$market}";
$uri .= $count ? "&count={$count}" : "";
$uri .= $to ? "&to={$to}" : "";
return $this->send(self::GET, $uri);
}
/**
* 계좌정보 조회
*
* @return array
*/
public function getAccounts(): array
{
$uri = "accounts";
return $this->send(self::GET, $uri);
}
/**
* 주문
*
* @param string $market
* @param string $side
* @param string $ord_type
* @param float|null $volume
* @param float|null $price
* @param string|null $identifier
* @return array
*/
public function order(string $market, string $side, string $ord_type, ?float $volume = null, ?float $price = null, ?string $identifier = null): array
{
$uri = "orders";
// 검증
if ($side == self::SIDE_BID && $ord_type == self::ORD_TYPE_MARKET) {
throw new Exception("매수 주문 인 경우 ord_type 이 market이 될 수 없습니다.");
} else if ($side == self::SIDE_ASK && $ord_type == self::ORD_TYPE_PRICE) {
throw new Exception("매도 주문 인 경우 ord_type 이 price가 될 수 없습니다.");
}
if ($side == self::SIDE_BID && $ord_type == self::ORD_TYPE_PRICE && (empty($price) || $price == 0)) {
throw new Exception("시장가 매수 주문 인 경우 price 의 값이 필요로 합니다.");
} else if ($side == self::SIDE_ASK && $ord_type == self::ORD_TYPE_MARKET && (empty($volume) || $volume == 0)) {
throw new Exception("시장가 매도 주문 인 경우 volume 의 값이 필요로 합니다.");
}
if ($identifier == null) {
$identifier = "{$market}_{$side}_{$ord_type}_" . date("Ymdhis");
}
$queryParam = [
"market" => $market,
"side" => $side,
"volume" => $volume ?? null,
"price" => $price ?? null,
"ord_type" => $ord_type,
"identifier" => $identifier,
];
return $this->send(self::POST, $uri, $queryParam);
}
/**
* 주문 취소
*
* @param string|null $uuid
* @param string|null $identifier
* @return array
*/
public function orderCancel(?string $uuid = null, ?string $identifier = null): array
{
$uri = "order";
// 검증
if ($uuid == null && $identifier == null) {
throw new Exception("uuid 또는 identifier 둘 중에 하나는 필요로 합니다.");
}
$queryParam = [
"uuid" => $uuid,
"identifier" => $identifier,
];
return $this->send(self::DELETE, $uri, $queryParam);
}
/**
* 주문 가능 정보
*
* @param string $market
* @return array
*/
public function orderChance(string $market): array
{
$uri = "orders/chance";
$queryParam = [
"market" => $market,
];
return $this->send(self::GET, $uri, $queryParam);
}
/**
* 주문 단건 조회
*
* @param string|null $uuid
* @param string|null $identifier
* @return array
*/
public function orderInfo(?string $uuid = null, ?string $identifier = null): array
{
$uri = "order";
// 검증
if ($uuid == null && $identifier == null) {
throw new Exception("uuid 또는 identifier 둘 중에 하나는 필요로 합니다.");
}
$queryParam = [
"uuid" => $uuid,
"identifier" => $identifier,
];
return $this->send(self::GET, $uri, $queryParam);
}
/**
* 주문 내역 조회
*
* @param string|null $market
* @param array|null $uuids
* @param array|null $identifiers
* @param string|null $state
* @param array|null $states
* @param integer|null $page
* @param integer|null $limit
* @param string|null $order_by
* @return array
*/
public function orderList(?string $market = null, ?array $uuids = null, ?array $identifiers = null, ?string $state = null, ?array $states = null, ?int $page = null, ?int $limit = null, ?string $order_by = null): array
{
$uri = "orders";
$queryParam = [
"market" => $market,
"uuids" => $uuids,
"identifiers" => $identifiers,
"state" => $state,
"states" => $states,
"page" => $page,
"limit" => $limit,
"order_by" => $order_by,
];
// 값이 없으면 키를 제거해준다.
$queryParam = array_filter($queryParam);
print_r($queryParam);
if (empty($queryParam)) {
$queryParam = null;
}
return $this->send(self::GET, $uri, $queryParam);
}
/**
* UPBIT 토큰 만드는 함수
*
* @param array|null $queryParam
* @return string
*/
private function makeToken(?array $queryParam = null): string
{
$addPayload = [];
if ($queryParam !== null) {
$queryString = http_build_query($queryParam);
$query_hash = hash('sha512', $queryString, false);
$addPayload = [
"query_hash" => $query_hash,
"query_hash_alg" => "SHA512",
];
}
$payload = [
"access_key" => $this->accessKey,
"nonce" => "SSR_" . uniqid(),
];
$mergePayload = array_merge($payload, $addPayload);
$token = JWT::encode($mergePayload, $this->secretKey);
return $token;
}
/**
* UPBIT API 서버와 통신
*
* @param string $method
* @param string $uri
* @param array|null $param
* @return array
*/
private function send(string $method = self::GET, string $uri, ?array $param = null): array
{
$token = $this->makeToken($param);
$form_params = array();
if ($param !== null) {
$form_params = array('form_params' => $param);
}
// basic option
$options = array(
'headers' => [
'Accept' => 'application/json',
'Authorization' => "Bearer " . $token,
],
);
if (!empty($form_params)) {
$options = array_merge($options, $form_params);
}
$response = $this->client->request($method, $uri, $options);
if ($response->getStatusCode() != self::HTTP_CODE_200) {
throw new Exception("Resopnse Code is not 200", 0);
}
return json_decode($response->getBody(), true);
}
}
부족한 실력으로 했기 때문에 더 좋은, 깔끔한 코드로 변경하셔서 사용하시면 될 듯합니다.