#!/usr/bin/php
<?php
/**
* 파일명: daemon_coin_container_240.php
* 기능: 업비트 24시간봉(일봉) 데이터 정순 수집 (과거 -> 현재)
* 수정사항: 4시간봉 로직을 24시간봉(일봉) 전용 로직으로 수정
*/
// 1. 환경 설정
date_default_timezone_set('Asia/Seoul');
set_time_limit(0);
if (php_sapi_name() !== 'cli') die("CLI 환경에서만 실행 가능합니다.\n");
// --- [수집 설정] ---
$MARKET = 'KRW-XRP';
// 파일명에서 확장자(.php)를 제외한 이름을 테이블명으로 사용
$TABLE_NAME = basename(__FILE__, '.php');
// [설정] 수집 범위 (시작점 KST -> 종료점 KST)
$START_KST = "2017-10-24 09:00:00";
$END_KST = "2026-01-19 12:10:00";
// 종료 지점 계산 (UTC 기준 타임스탬프)
$end_dt_obj = new DateTime($END_KST, new DateTimeZone('Asia/Seoul'));
$end_dt_obj->setTimezone(new DateTimeZone('UTC'));
$END_TS_UTC = $end_dt_obj->getTimestamp();
// --- [루프/백오프 설정] ---
$SLEEP_USEC_NORMAL = 500000;
$SLEEP_USEC_EMPTY = 1000000;
$SLEEP_SEC_DBFAIL = 10;
$SLEEP_SEC_APIFAIL = 10;
$SLEEP_SEC_RETRY = 2;
$SLEEP_SEC_FATAL = 300;
// --- [함수: DB 연결] ---
function get_db_connection() {
try {
$db_upbit = null; $pdo = null;
$db_file = '/home/www/DB/db_upbit.php';
if (file_exists($db_file)) {
include $db_file;
}
$conn = $db_upbit ?? $pdo ?? null;
if ($conn instanceof PDO) {
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $conn;
}
} catch(Throwable $e) {}
return null;
}
// --- [함수: API 호출] ---
function fetch_upbit_candles($market, $to_iso, $count = 200) {
$to_param = str_replace(['T', 'Z'], [' ', ''], $to_iso);
// [수정] 24시간봉(일봉) 수집 URL로 변경
$url = sprintf(
"https://api.upbit.com/v1/candles/days?market=%s&count=%d&to=%s",
$market, $count, urlencode($to_param)
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Upbit Daemon)');
$res = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code === 429) return 'RETRY';
if ($code !== 200 || !$res) return null;
$json = json_decode($res, true);
if (!is_array($json)) return null;
return $json;
}
// --- [데몬화 시작] ---
$pid = pcntl_fork();
if ($pid == -1) die("Fork Error\n");
if ($pid) exit(0);
if (posix_setsid() == -1) die("Session Error\n");
$pid = pcntl_fork();
if ($pid == -1) die("Fork2 Error\n");
if ($pid) exit(0);
umask(0); chdir('/');
fclose(STDIN); fclose(STDOUT); fclose(STDERR);
$stdin = fopen('/dev/null', 'r');
$stdout = fopen('/dev/null', 'wb');
$stderr = fopen('/dev/null', 'wb');
$stop = false;
pcntl_async_signals(true);
pcntl_signal(SIGTERM, function() { global $stop; $stop = true; });
pcntl_signal(SIGINT, function() { global $stop; $stop = true; });
$pdo_conn = null;
$current_pointer_utc = null;
$empty_streak = 0;
$is_finished = false;
while (!$stop) {
if (!($pdo_conn instanceof PDO)) {
$pdo_conn = get_db_connection();
if (!$pdo_conn) { sleep($SLEEP_SEC_DBFAIL); continue; }
$current_pointer_utc = null;
$empty_streak = 0;
}
if ($is_finished) {
sleep(60);
continue;
}
try {
if ($current_pointer_utc === null) {
$stmt = $pdo_conn->prepare("SELECT MAX(candle_date_time_utc) FROM {$TABLE_NAME} WHERE market = ?");
$stmt->execute([$MARKET]);
$max_utc = $stmt->fetchColumn();
if ($max_utc) {
$dt = new DateTime($max_utc, new DateTimeZone('UTC'));
$dt->setTime($dt->format('H'), 0, 0);
$current_pointer_utc = $dt->format('Y-m-d\TH:i:s\Z');
} else {
$dt = new DateTime($START_KST, new DateTimeZone('Asia/Seoul'));
$dt->setTimezone(new DateTimeZone('UTC'));
$current_pointer_utc = $dt->format('Y-m-d\TH:i:s\Z');
}
}
$pointer_dt = new DateTime($current_pointer_utc, new DateTimeZone('UTC'));
$pointer_ts = $pointer_dt->getTimestamp();
if ($pointer_ts >= $END_TS_UTC) {
$is_finished = true;
continue;
}
// 수집 대상 시점 설정 (200개 캔들 분량 = 4800시간 미래 방향 설정)
$api_dt = clone $pointer_dt;
$api_dt->modify('+4800 hours');
$target_to = $api_dt->format('Y-m-d\TH:i:s\Z');
$candles = fetch_upbit_candles($MARKET, $target_to, 200);
if ($candles === 'RETRY') { sleep($SLEEP_SEC_RETRY); continue; }
if ($candles === null) { sleep($SLEEP_SEC_APIFAIL); continue; }
// 데이터 호출 실패 시 재시도 로직 (5회 연속 실패 시 다음 24시간으로 전진)
if (empty($candles)) {
$empty_streak++;
if ($empty_streak < 5) {
usleep($SLEEP_USEC_EMPTY);
continue;
} else {
$empty_streak = 0;
// [수정] 24시간 전진 로직 적용
$pointer_dt->modify('+24 hours');
$current_pointer_utc = $pointer_dt->format('Y-m-d\TH:i:s\Z');
continue;
}
}
$empty_streak = 0;
$pdo_conn->beginTransaction();
$sql = "INSERT INTO {$TABLE_NAME} (
market, candle_date_time_utc, candle_date_time_kst,
opening_price, high_price, low_price, trade_price,
timestamp, candle_acc_trade_price, candle_acc_trade_volume,
unit, prev_closing_price, change_price, change_rate
) VALUES (
:market, :utc, :kst, :open, :high, :low, :close,
:ts, :acc_price, :acc_vol, :unit, :prev_close, :chg_price, :chg_rate
)
ON DUPLICATE KEY UPDATE
opening_price = VALUES(opening_price),
high_price = VALUES(high_price),
low_price = VALUES(low_price),
trade_price = VALUES(trade_price),
timestamp = VALUES(timestamp),
candle_acc_trade_price = VALUES(candle_acc_trade_price),
candle_acc_trade_volume = VALUES(candle_acc_trade_volume),
unit = VALUES(unit),
prev_closing_price = VALUES(prev_closing_price),
change_price = VALUES(change_price),
change_rate = VALUES(change_rate)";
$stmt_ins = $pdo_conn->prepare($sql);
usort($candles, function($a, $b) {
return strcmp($a['candle_date_time_utc'], $b['candle_date_time_utc']);
});
$latest_utc = null;
foreach ($candles as $c) {
$utc_val = str_replace('T', ' ', $c['candle_date_time_utc']);
$c_ts = strtotime($utc_val . ' UTC');
if ($c_ts <= $pointer_ts) continue;
if ($c_ts > $END_TS_UTC) break;
$stmt_ins->execute([
':market' => $c['market'],
':utc' => $utc_val,
':kst' => str_replace('T', ' ', $c['candle_date_time_kst']),
':open' => (double)$c['opening_price'],
':high' => (double)$c['high_price'],
':low' => (double)$c['low_price'],
':close' => (double)$c['trade_price'],
':ts' => (int)$c['timestamp'],
':acc_price' => (double)$c['candle_acc_trade_price'],
':acc_vol' => (double)$c['candle_acc_trade_volume'],
':unit' => (int)($c['unit'] ?? 1440),
':prev_close'=> (double)($c['prev_closing_price'] ?? 0),
':chg_price' => (double)($c['change_price'] ?? 0),
':chg_rate' => (double)($c['change_rate'] ?? 0)
]);
$latest_utc = $c['candle_date_time_utc'];
}
$pdo_conn->commit();
if ($latest_utc) {
$current_pointer_utc = (strpos($latest_utc, 'Z') === false) ? ($latest_utc . 'Z') : $latest_utc;
} else {
// 중복 데이터 시 24시간 전진
$pointer_dt->modify('+24 hours');
$current_pointer_utc = $pointer_dt->format('Y-m-d\TH:i:s\Z');
}
usleep($SLEEP_USEC_NORMAL);
} catch (Throwable $e) {
if ($pdo_conn instanceof PDO && $pdo_conn->inTransaction()) $pdo_conn->rollBack();
$pdo_conn = null;
sleep($SLEEP_SEC_FATAL);
}
}
exit(0);