-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun.sh
More file actions
executable file
·666 lines (578 loc) · 22.7 KB
/
Copy pathrun.sh
File metadata and controls
executable file
·666 lines (578 loc) · 22.7 KB
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
#!/usr/bin/env sh
# =============================================================================
# run.sh — TradingSystem Main Entrypoint
# =============================================================================
# Usage:
# ./run.sh [COMMAND] [OPTIONS]
#
# Commands:
# start Run in foreground (interactive)
# start -d|--detach Run in background via nohup
# start --paper Paper-trading mode (combinable with -d)
# stop Send SIGTERM to a running detached process
# status Show whether a detached process is currently running
# close-all Force close all positions (emergency exit)
#
# logs Alias for 'logs list'
# logs list [-a|--all] List output log files (--all includes verbose logs)
# logs tail [-v] Tail today's log (--verbose for trading log)
# logs today [-v] Show today's full log (--verbose for trading log)
# logs yesterday [-v] Show yesterday's full log (--verbose for trading log)
# logs <N> [-v] Show log from N days ago (--verbose for trading log)
# logs clean [-n N] Remove logs older than N days (cleans both output & verbose)
#
# cron Install cron job (auto-start at 9 AM Mon–Fri)
# uncron Remove installed cron job
#
# install [NAME] Symlink this script to /usr/local/bin/<NAME> (default: ts)
#
# All commands can be selected interactively if no argument is provided.
# =============================================================================
set -eu
# Attempt to enable pipefail if the shell supports it (like bash or busybox ash)
if (set -o pipefail) 2>/dev/null; then
set -o pipefail
fi
# ── Paths ──────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
VENV_PYTHON="${VENV_PYTHON:-$SCRIPT_DIR/.venv/bin/python}"
LOGS_DIR="$SCRIPT_DIR/logs"
PID_FILE="$SCRIPT_DIR/.trading.pid"
TODAY="$(date +%Y%m%d)"
TODAY_LOG="$LOGS_DIR/output_${TODAY}.log"
CRON_TAG="# TradingSystem-autostart"
CRON_LOG="$LOGS_DIR/cron.log"
# ── ANSI colours ───────────────────────────────────────────────────────────
BOLD="\033[1m"
DIM="\033[2m"
CYAN="\033[36m"
GREEN="\033[32m"
YELLOW="\033[33m"
RED="\033[31m"
BLUE="\033[34m"
MAGENTA="\033[35m"
RESET="\033[0m"
# ── Helpers ────────────────────────────────────────────────────────────────
banner() {
printf '%b\n' ""
printf '%b\n' "${BOLD}${CYAN}╔══════════════════════════════════════════════╗${RESET}"
printf '%b\n' "${BOLD}${CYAN}║ 📈 TradingSystem Launcher ║${RESET}"
printf '%b\n' "${BOLD}${CYAN}╚══════════════════════════════════════════════╝${RESET}"
printf '%b\n' ""
}
info() { printf '%b\n' "${CYAN}[INFO]${RESET} $*"; }
success() { printf '%b\n' "${GREEN}[OK]${RESET} $*"; }
warn() { printf '%b\n' "${YELLOW}[WARN]${RESET} $*"; }
error() { printf '%b\n' "${RED}[ERROR]${RESET} $*"; }
header() { printf '%b\n' "\n${BOLD}${BLUE}▶ $*${RESET}"; }
require_venv() {
if [ ! -x "$VENV_PYTHON" ] && ! command -v "$VENV_PYTHON" > /dev/null 2>&1; then
error "Virtual environment not found at: $VENV_PYTHON"
error "Run: python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt"
exit 1
fi
}
is_container() {
if [ -f /.dockerenv ] || [ -f /run/.containerenv ]; then
error "This method is not supported when running inside a container."
error "Containers should run the main process in the foreground."
exit 1
fi
}
get_pid() {
if [ -f "$PID_FILE" ]; then
cat "$PID_FILE"
fi
}
is_running() {
local pid
pid="$(get_pid)"
[ -n "$pid" ] && kill -0 "$pid" 2>/dev/null
}
ensure_logs_dir() {
mkdir -p "$LOGS_DIR"
}
# Returns the date string N days ago in YYYYMMDD format
date_n_days_ago() {
date -d "$1 days ago" +%Y%m%d 2>/dev/null || date -v-"${1}d" +%Y%m%d 2>/dev/null
}
# ── Mode implementations ───────────────────────────────────────────────────
do_start() {
local paper_flag=""
local detach_flag=""
# Parse flags
for arg in "$@"; do
case "$arg" in
--paper) paper_flag="--paper" ;;
-d|--detach) detach_flag="1" ;;
esac
done
if [ -n "$detach_flag" ]; then
do_detach "$paper_flag"
return
fi
require_venv
ensure_logs_dir
if [ -n "$paper_flag" ]; then
warn "Paper-trading mode enabled — no real orders will be placed."
else
info "Live-trading mode."
fi
info "Starting in foreground (Ctrl+C to stop)…"
printf '%b\n' "${DIM}─────────────────────────────────────────────${RESET}"
cd "$SCRIPT_DIR"
exec "$VENV_PYTHON" main.py $paper_flag
}
do_detach() {
local paper_flag="${1:-}"
# Should not run detached mode inside a container
is_container
require_venv
ensure_logs_dir
if is_running; then
warn "A detached process is already running (PID $(get_pid))."
warn "Run './run.sh stop' first, or './run.sh status' to inspect."
exit 1
fi
local nohup_log="$TODAY_LOG"
if [ -n "$paper_flag" ]; then
warn "Paper-trading mode enabled — no real orders will be placed."
fi
info "Starting detached process…"
info "Output appended to: ${BOLD}$nohup_log${RESET}"
cd "$SCRIPT_DIR"
# stdbuf -eL -oL ensures line buffering even when output is redirected to a file
nohup stdbuf -eL -oL "$VENV_PYTHON" main.py $paper_flag >> "$nohup_log" 2>&1 &
local pid=$!
echo "$pid" > "$PID_FILE"
# Give it a moment and verify it started
sleep 1
if kill -0 "$pid" 2>/dev/null; then
success "Process started successfully (PID ${BOLD}$pid${RESET})."
info "Use './run.sh logs tail' to follow logs."
info "Use './run.sh stop' to stop the process."
else
error "Process failed to start. Check logs: $nohup_log"
rm -f "$PID_FILE"
exit 1
fi
}
do_stop() {
if ! is_running; then
warn "No detached process appears to be running."
[ -f "$PID_FILE" ] && { warn "Stale PID file found — removing."; rm -f "$PID_FILE"; }
exit 0
fi
local pid
pid="$(get_pid)"
info "Sending SIGTERM to process $pid…"
kill -TERM "$pid"
# Wait up to 10s for graceful shutdown
local waited=0
while kill -0 "$pid" 2>/dev/null && [ "$waited" -lt 10 ]; do
sleep 1
waited=$(( waited + 1 ))
done
if kill -0 "$pid" 2>/dev/null; then
warn "Process did not exit in 10s — sending SIGKILL."
kill -KILL "$pid" 2>/dev/null || true
fi
rm -f "$PID_FILE"
success "Process stopped."
}
do_status() {
header "Detached Process Status"
if is_running; then
local pid
pid="$(get_pid)"
success "Running — PID ${BOLD}$pid${RESET}"
printf '%b\n' "${DIM}$(ps -p "$pid" -o pid,etime,pcpu,pmem,cmd --no-headers 2>/dev/null || true)${RESET}"
else
warn "Not running."
[ -f "$PID_FILE" ] && { warn "Stale PID file found — removing."; rm -f "$PID_FILE"; }
fi
}
# ── Log subcommands ────────────────────────────────────────────────────────
do_logs() {
local subcmd="${1:-list}"
shift 2>/dev/null || true
case "$subcmd" in
list) do_logs_list "$@" ;;
tail) do_logs_tail "$@" ;;
today) do_logs_day 0 "$@" ;;
yesterday) do_logs_day 1 "$@" ;;
clean) do_logs_clean "$@" ;;
*)
# Check if it's a number (N days ago)
if echo "$subcmd" | grep -qE '^[0-9]+$'; then
do_logs_day "$subcmd" "$@"
else
error "Unknown logs subcommand: '$subcmd'"
printf '%b\n' ""
printf '%b\n' "${BOLD}Usage:${RESET} $0 logs [SUBCOMMAND]"
printf '%b\n' ""
printf '%b\n' "${BOLD}Subcommands:${RESET}"
printf '%b\n' " ${CYAN}list${RESET} List output log files (default)"
printf '%b\n' " ${CYAN}list -a|--all${RESET} List all log files (including verbose)"
printf '%b\n' " ${CYAN}tail [-v]${RESET} Follow today's log (-v for verbose)"
printf '%b\n' " ${CYAN}today [-v]${RESET} Show today's full log (-v for verbose)"
printf '%b\n' " ${CYAN}yesterday [-v]${RESET} Show yesterday's full log (-v for verbose)"
printf '%b\n' " ${CYAN}<N> [-v]${RESET} Show log from N days ago (-v for verbose)"
printf '%b\n' " ${CYAN}clean [-n N]${RESET} Remove logs older than N days (both output & verbose)"
printf '%b\n' ""
exit 1
fi
;;
esac
}
do_logs_list() {
local show_all=0
for arg in "$@"; do
case "$arg" in
-a|--all) show_all=1 ;;
esac
done
ensure_logs_dir
local files
if [ "$show_all" -eq 1 ]; then
header "All Log Files in $LOGS_DIR"
files=$(find "$LOGS_DIR" -maxdepth 1 \( -name "output_*.log" -o -name "trading_*.log" \) | sort -r)
else
header "Output Log Files in $LOGS_DIR"
files=$(find "$LOGS_DIR" -maxdepth 1 -name "output_*.log" | sort -r)
fi
if [ -z "$files" ]; then
warn "No log files found."
[ "$show_all" -eq 0 ] && info "Use ${BOLD}logs list --all${RESET} to include verbose trading logs."
return
fi
printf '%b\n' "${BOLD}${MAGENTA}$(printf '%-5s %-30s %10s' '#' 'File' 'Size')${RESET}"
printf '%b\n' "${DIM}──────────────────────────────────────────────${RESET}"
local i=1
local yesterday
yesterday="$(date_n_days_ago 1)"
for f in $files; do
local fname size marker
fname="$(basename "$f")"
size="$(du -sh "$f" 2>/dev/null | cut -f1)"
marker=""
[ "$fname" = "output_${TODAY}.log" ] && marker=" ${GREEN}← today${RESET}"
[ "$fname" = "output_${yesterday}.log" ] && marker=" ${YELLOW}← yesterday${RESET}"
[ "$fname" = "trading_${TODAY}.log" ] && marker=" ${GREEN}← today (verbose)${RESET}"
[ "$fname" = "trading_${yesterday}.log" ] && marker=" ${YELLOW}← yesterday (verbose)${RESET}"
printf "%-5s %-30s %10s%b\n" "$i" "$fname" "$size" "$marker"
i=$(( i + 1 ))
done
echo ""
[ "$show_all" -eq 0 ] && info "Use ${BOLD}logs list --all${RESET} to include verbose trading logs." || true
}
do_logs_day() {
local n="${1:-0}"
shift 2>/dev/null || true
local verbose=0
for arg in "$@"; do
case "$arg" in
-v|--verbose) verbose=1 ;;
esac
done
ensure_logs_dir
local target_date target_log label prefix type_label
target_date="$(date_n_days_ago "$n")"
if [ "$verbose" -eq 1 ]; then
prefix="trading"
type_label="Verbose Log"
else
prefix="output"
type_label="Log"
fi
target_log="$LOGS_DIR/${prefix}_${target_date}.log"
if [ "$n" -eq 0 ]; then
label="today"
elif [ "$n" -eq 1 ]; then
label="yesterday"
else
label="${n} days ago"
fi
if [ ! -f "$target_log" ]; then
warn "No ${prefix} log file found for $label ($target_log)."
exit 1
fi
header "$type_label — $label ($target_date)"
printf '%b\n' "${DIM}─────────────────────────────────────────────${RESET}"
cat "$target_log"
}
do_logs_tail() {
local verbose=0
for arg in "$@"; do
case "$arg" in
-v|--verbose) verbose=1 ;;
esac
done
ensure_logs_dir
local target_log type_label
if [ "$verbose" -eq 1 ]; then
target_log="$LOGS_DIR/trading_${TODAY}.log"
type_label="Verbose"
else
target_log="$TODAY_LOG"
type_label="Output"
fi
if [ ! -f "$target_log" ]; then
warn "No log file found for today ($target_log)."
warn "The file will be created once the process starts."
exit 1
fi
header "Tailing Today's $type_label Log — $target_log"
info "Press Ctrl+C to stop following."
printf '%b\n' "${DIM}─────────────────────────────────────────────${RESET}"
tail -f "$target_log"
}
do_logs_clean() {
ensure_logs_dir
# Parse -n flag (default: 0 = remove everything except today)
local days=0
while [ $# -gt 0 ]; do
case "$1" in
-n)
shift
if [ $# -eq 0 ] || ! echo "$1" | grep -qE '^[0-9]+$'; then
error "Option -n requires a numeric argument."
exit 1
fi
days="$1"
;;
*)
error "Unknown option: '$1'"
exit 1
;;
esac
shift
done
local cutoff_date
cutoff_date="$(date_n_days_ago "$days")"
header "Cleaning Logs"
if [ "$days" -eq 0 ]; then
info "Removing all log files except today's (output + verbose)…"
else
info "Removing log files older than $days day(s) (before $cutoff_date)…"
fi
local count=0
for f in "$LOGS_DIR"/output_*.log "$LOGS_DIR"/trading_*.log; do
[ -f "$f" ] || continue
local fname file_date
fname="$(basename "$f")"
# Extract YYYYMMDD from output_YYYYMMDD.log or trading_YYYYMMDD.log
file_date="$(echo "$fname" | sed 's/^\(output\|trading\)_//;s/\.log$//')"
# Keep files on or after the cutoff date
if [ "$file_date" -lt "$cutoff_date" ]; then
rm -f "$f"
info "Removed: $fname"
count=$(( count + 1 ))
fi
done
if [ "$count" -eq 0 ]; then
warn "No log files to clean."
else
success "Removed $count log file(s)."
fi
}
do_cron() {
# Should not run cron jobs inside a container
is_container
# Full path used inside cron since it runs without the user's PATH
local cron_cmd="cd \"$SCRIPT_DIR\" && \"$SCRIPT_DIR/run.sh\" start -d >> \"$CRON_LOG\" 2>&1"
local cron_line="0 9 * * 1-5 $cron_cmd $CRON_TAG"
if crontab -l 2>/dev/null | grep -qF "$CRON_TAG"; then
warn "A TradingSystem cron job already exists:"
printf '%b\n' "${DIM}$(crontab -l 2>/dev/null | grep -F "$CRON_TAG")${RESET}"
echo ""
printf '%b' " ${BOLD}Overwrite it? [y/N]:${RESET} "
read -r answer
echo "$answer" | grep -iq "^y" || { info "Aborted."; return; }
# Remove existing before re-adding
(crontab -l 2>/dev/null | grep -vF "$CRON_TAG") | crontab -
fi
(crontab -l 2>/dev/null; echo "$cron_line") | crontab -
success "Cron job installed:"
printf '%b\n' " ${DIM}$cron_line${RESET}"
info "Runs every weekday (Mon–Fri) at 09:00 AM."
info "Cron output → $CRON_LOG"
}
do_uncron() {
if ! crontab -l 2>/dev/null | grep -qF "$CRON_TAG"; then
warn "No TradingSystem cron job found — nothing to remove."
return
fi
info "Removing cron job…"
(crontab -l 2>/dev/null | grep -vF "$CRON_TAG") | crontab -
success "Cron job removed."
}
do_close_all() {
require_venv
ensure_logs_dir
local yes_flag=""
[ "${1:-}" = "--yes" ] && yes_flag="--yes"
if [ -z "$yes_flag" ]; then
warn "EMERGENCY EXIT: This will force close all open positions!"
printf '%b' " ${BOLD}Are you sure you want to proceed? [y/N]:${RESET} "
read -r answer
echo "$answer" | grep -iq "^y" || { info "Aborted."; exit 0; }
fi
info "Executing force close..."
cd "$SCRIPT_DIR"
"$VENV_PYTHON" main.py --force-close
success "Force close executed."
}
do_install() {
local name="${1:-ts}"
local bin_dir="/usr/local/bin"
local target="$bin_dir/$name"
local script_path
script_path="$(readlink -f "$0")"
header "Installing Symlink"
if [ -e "$target" ] && [ ! -L "$target" ]; then
error "'$target' already exists and is not a symlink. Refusing to overwrite."
exit 1
fi
if [ -L "$target" ]; then
local existing
existing="$(readlink -f "$target")"
if [ "$existing" = "$script_path" ]; then
warn "Symlink already exists: $target → $script_path"
return
fi
warn "Symlink '$target' points to '$existing' — overwriting."
fi
info "Creating symlink: ${BOLD}$target${RESET} → ${BOLD}$script_path${RESET}"
if [ -w "$bin_dir" ]; then
ln -sf "$script_path" "$target"
else
info "Requires elevated privileges…"
sudo ln -sf "$script_path" "$target"
fi
success "Installed! You can now run '${BOLD}${GREEN}$name${RESET}' from anywhere."
info "Example: ${DIM}$name start${RESET} or ${DIM}$name logs tail${RESET}"
}
# ── Interactive menu ───────────────────────────────────────────────────────
interactive_menu() {
banner
printf '%b\n' " ${BOLD}Select a mode:${RESET}"
echo ""
printf '%b\n' " ${CYAN}1)${RESET} Start interactive ${DIM}(foreground, output to terminal)${RESET}"
printf '%b\n' " ${CYAN}2)${RESET} Start interactive paper ${DIM}(foreground, paper-trading)${RESET}"
printf '%b\n' " ${CYAN}3)${RESET} Start detached ${DIM}(background via nohup)${RESET}"
printf '%b\n' " ${CYAN}4)${RESET} Start detached paper ${DIM}(background, paper-trading)${RESET}"
printf '%b\n' " ${CYAN}5)${RESET} Show log list"
printf '%b\n' " ${CYAN}6)${RESET} Show today's log"
printf '%b\n' " ${CYAN}7)${RESET} Tail today's log ${DIM}(follow mode)${RESET}"
printf '%b\n' " ${CYAN}8)${RESET} Status ${DIM}(check detached process)${RESET}"
printf '%b\n' " ${CYAN}9)${RESET} Stop detached process"
printf '%b\n' " ${CYAN}x)${RESET} Force close all positions ${DIM}(emergency exit)${RESET}"
printf '%b\n' " ${CYAN}c)${RESET} Install cron job ${DIM}(auto-start detach at 9 AM Mon–Fri)${RESET}"
printf '%b\n' " ${CYAN}r)${RESET} Remove cron job"
printf '%b\n' " ${CYAN}i)${RESET} Install global symlink ${DIM}(add 'ts' to PATH)${RESET}"
printf '%b\n' " ${CYAN}0)${RESET} Exit"
echo ""
printf '%b' " ${BOLD}Enter choice [0-9/x/c/r/i]:${RESET} "
read -r choice
case "$choice" in
1) do_start ;;
2) do_start --paper ;;
3) do_start -d ;;
4) do_start -d --paper ;;
5) do_logs list ;;
6) do_logs today ;;
7) do_logs tail ;;
8) do_status ;;
9) do_stop ;;
x) do_close_all ;;
c) do_cron ;;
r) do_uncron ;;
i) do_install ;;
0) printf '%b\n' "${DIM}Bye.${RESET}"; exit 0 ;;
*) error "Invalid choice: '$choice'"; exit 1 ;;
esac
}
# ── Argument parsing & dispatch ────────────────────────────────────────────
usage() {
printf '%b\n' ""
printf '%b\n' "${BOLD}Usage:${RESET} $0 [COMMAND] [OPTIONS]"
printf '%b\n' ""
printf '%b\n' "${BOLD}Commands:${RESET}"
printf '%b\n' " ${CYAN}start${RESET} Run in foreground (interactive)"
printf '%b\n' " ${CYAN}start -d|--detach${RESET} Run in background via nohup"
printf '%b\n' " ${CYAN}stop${RESET} Stop a running detached process"
printf '%b\n' " ${CYAN}status${RESET} Show detached process status"
printf '%b\n' " ${CYAN}close-all${RESET} Force close all positions (emergency exit)"
printf '%b\n' ""
printf '%b\n' " ${CYAN}logs${RESET} List output log files (alias for 'logs list')"
printf '%b\n' " ${CYAN}logs list [-a]${RESET} List output logs (--all includes verbose trading logs)"
printf '%b\n' " ${CYAN}logs tail [-v]${RESET} Follow today's log (--verbose for trading log)"
printf '%b\n' " ${CYAN}logs today [-v]${RESET} Show today's full log (--verbose for trading log)"
printf '%b\n' " ${CYAN}logs yesterday [-v]${RESET} Show yesterday's log (--verbose for trading log)"
printf '%b\n' " ${CYAN}logs <N> [-v]${RESET} Show log from N days ago (--verbose for trading log)"
printf '%b\n' " ${CYAN}logs clean [-n N]${RESET} Remove logs older than N days (cleans both output & verbose)"
printf '%b\n' ""
printf '%b\n' " ${CYAN}cron${RESET} Install cron job (auto-start at 9 AM Mon–Fri)"
printf '%b\n' " ${CYAN}uncron${RESET} Remove the installed cron job"
printf '%b\n' " ${CYAN}install [NAME]${RESET} Symlink to /usr/local/bin/<NAME> for global access (default: ts)"
printf '%b\n' ""
printf '%b\n' "${BOLD}Options:${RESET}"
printf '%b\n' " ${CYAN}--paper${RESET} Enable paper-trading mode (applies to start)"
printf '%b\n' " ${CYAN}--yes${RESET} Skip confirmation for close-all"
printf '%b\n' " ${CYAN}--help${RESET} Show this help message"
printf '%b\n' ""
printf '%b\n' "${DIM}Run without arguments for the interactive menu.${RESET}"
printf '%b\n' ""
}
MODE="${1:-}"
case "$MODE" in
"")
interactive_menu
;;
start)
banner
shift
do_start "$@"
;;
stop)
banner
do_stop
;;
close-all)
banner
shift
do_close_all "${1:-}"
;;
status)
banner
do_status
;;
logs)
banner
shift
do_logs "$@"
;;
cron)
banner
do_cron
;;
uncron)
banner
do_uncron
;;
install)
banner
shift
do_install "${1:-ts}"
;;
--help|-h|help)
banner
usage
;;
*)
error "Unknown command: '$MODE'"
usage
exit 1
;;
esac