feat(vol_breakout): 15m 현물 롱 라이브·모니터·cron 운영 추가
vol_breakout 멀티종목 tick, vol_live HTML 모니터, 마감 봉만 저장하는 캔들 다운로드, 텔레그램 체결 알림, cron/watch 감시 스크립트 및 테스트를 포함한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
67
scripts/_cron_env.sh
Executable file
67
scripts/_cron_env.sh
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
# cron 래퍼 공통 — 프로젝트 루트, PYTHONPATH, 인터프리터 탐색.
|
||||
# shellcheck disable=SC2034
|
||||
set -euo pipefail
|
||||
|
||||
_CRON_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CRON_PROJECT_ROOT="$(cd "${_CRON_SCRIPT_DIR}/.." && pwd)"
|
||||
cd "${CRON_PROJECT_ROOT}"
|
||||
export PYTHONPATH=src
|
||||
|
||||
# 로그 디렉터리 (cron 리다이렉트 전에 mkdir -p 가능)
|
||||
ensure_cron_log_dir() {
|
||||
local dir="$1"
|
||||
mkdir -p "$dir"
|
||||
}
|
||||
|
||||
# python-dotenv + pandas 등 프로젝트 의존성이 있는 python3
|
||||
resolve_bithumb_python() {
|
||||
local candidate=""
|
||||
for candidate in \
|
||||
"${BITHUMB_PYTHON:-}" \
|
||||
"${HOME}/opt/anaconda3/envs/coin/bin/python3" \
|
||||
"${HOME}/opt/anaconda3/envs/ncue/bin/python3" \
|
||||
"${HOME}/miniconda3/envs/xavis/bin/python3" \
|
||||
"$(command -v python3 2>/dev/null || true)"; do
|
||||
if [ -n "$candidate" ] && [ -x "$candidate" ] \
|
||||
&& "$candidate" -c "import dotenv" 2>/dev/null; then
|
||||
echo "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] python-dotenv 가능한 python3를 찾지 못함 (BITHUMB_PYTHON 설정)" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
# mkdir 기반 잠금 — stale/hung 프로세스 정리 후 획득
|
||||
# 사용: acquire_cron_lock LOCKDIR pgrep_pattern MAX_AGE_SEC
|
||||
acquire_cron_lock() {
|
||||
local lockdir="$1"
|
||||
local pgrep_pattern="$2"
|
||||
local max_age_sec="${3:-900}"
|
||||
local now_ts pid elapsed lock_mtime
|
||||
|
||||
now_ts="$(date +%s)"
|
||||
if [ -d "$lockdir" ]; then
|
||||
lock_mtime="$(stat -f %m "$lockdir" 2>/dev/null || stat -c %Y "$lockdir" 2>/dev/null || echo 0)"
|
||||
elapsed=$((now_ts - lock_mtime))
|
||||
if pgrep -f "$pgrep_pattern" >/dev/null 2>&1; then
|
||||
if [ "$elapsed" -gt "$max_age_sec" ]; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') [WARN] hung ${pgrep_pattern} (${elapsed}s) — 종료 후 lock 정리" >&2
|
||||
pkill -f "$pgrep_pattern" 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
fi
|
||||
if ! pgrep -f "$pgrep_pattern" >/dev/null 2>&1; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') [WARN] stale lock 정리: ${lockdir}" >&2
|
||||
rmdir "$lockdir" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! mkdir "$lockdir" 2>/dev/null; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') [SKIP] ${pgrep_pattern} 실행 중 (lock ${lockdir})" >&2
|
||||
return 1
|
||||
fi
|
||||
trap 'rmdir "'"$lockdir"'" 2>/dev/null || true' EXIT INT TERM
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user