修复复盘
This commit is contained in:
@@ -59,8 +59,10 @@ from history_window_lib import (
|
||||
PRESET_UTC_LAST7D,
|
||||
PRESET_UTC_TODAY,
|
||||
list_window_redirect_query,
|
||||
normalize_bj_datetime_storage,
|
||||
resolve_list_window,
|
||||
resolve_window,
|
||||
sql_list_time_field,
|
||||
utc_window_to_bj_sql_strings,
|
||||
)
|
||||
|
||||
@@ -5653,9 +5655,9 @@ def render_main_page(page="trade"):
|
||||
order_list = []
|
||||
for o in raw_order_list:
|
||||
order_list.append(enrich_order_item(row_to_dict(o), current_capital))
|
||||
tr_ts = sql_list_time_field("closed_at", "created_at", "opened_at")
|
||||
raw_records = conn.execute(
|
||||
"SELECT * FROM trade_records WHERE COALESCE(closed_at, created_at, opened_at) >= ? "
|
||||
"AND COALESCE(closed_at, created_at, opened_at) <= ? ORDER BY id DESC LIMIT 1000",
|
||||
f"SELECT * FROM trade_records WHERE {tr_ts} >= ? AND {tr_ts} <= ? ORDER BY id DESC LIMIT 1000",
|
||||
(start_bj, end_bj),
|
||||
).fetchall()
|
||||
records = [to_effective_trade_dict(r) for r in raw_records]
|
||||
@@ -6834,8 +6836,8 @@ def export_trade_records():
|
||||
"margin_capital,leverage,pnl_amount,hold_seconds,hold_minutes,planned_rr,actual_rr,risk_amount,"
|
||||
"opened_at,closed_at,result,miss_reason,entry_reason,reviewed_entry_reason,"
|
||||
"exchange_realized_pnl,exchange_opened_at,exchange_closed_at,created_at "
|
||||
"FROM trade_records WHERE COALESCE(closed_at, created_at, opened_at) >= ? "
|
||||
"AND COALESCE(closed_at, created_at, opened_at) <= ? ORDER BY id ASC",
|
||||
f"FROM trade_records WHERE {sql_list_time_field('closed_at', 'created_at', 'opened_at')} >= ? "
|
||||
f"AND {sql_list_time_field('closed_at', 'created_at', 'opened_at')} <= ? ORDER BY id ASC",
|
||||
(start_bj, end_bj),
|
||||
).fetchall()
|
||||
conn.close()
|
||||
@@ -7239,7 +7241,12 @@ def add_journal():
|
||||
new_trade_while_occupied, note, image)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
(
|
||||
entry_id, d.get("open_datetime"), d.get("close_datetime"), hold_duration, d.get("coin"), d.get("tf"),
|
||||
entry_id,
|
||||
normalize_bj_datetime_storage(d.get("open_datetime")),
|
||||
normalize_bj_datetime_storage(d.get("close_datetime")),
|
||||
hold_duration,
|
||||
d.get("coin"),
|
||||
d.get("tf"),
|
||||
d.get("pnl"), entry_reason_norm, exit_reason_stored, d.get("expect_rr"), real_rr_text,
|
||||
early_exit_raw, early_exit_reason_saved, early_exit_trigger, early_exit_note,
|
||||
None, None, None, mood_issues,
|
||||
@@ -7261,9 +7268,9 @@ def api_journals():
|
||||
win = _list_window_from_request()
|
||||
start_bj, end_bj = utc_window_to_bj_sql_strings(win["start_utc"], win["end_utc"], APP_TZ)
|
||||
conn = get_db()
|
||||
j_ts = sql_list_time_field("close_datetime", "created_at", "open_datetime")
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM journal_entries WHERE COALESCE(close_datetime, created_at, open_datetime) >= ? "
|
||||
"AND COALESCE(close_datetime, created_at, open_datetime) <= ? ORDER BY created_at DESC LIMIT 500",
|
||||
f"SELECT * FROM journal_entries WHERE {j_ts} >= ? AND {j_ts} <= ? ORDER BY created_at DESC LIMIT 500",
|
||||
(start_bj, end_bj),
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
@@ -60,8 +60,10 @@ from history_window_lib import (
|
||||
PRESET_UTC_LAST7D,
|
||||
PRESET_UTC_TODAY,
|
||||
list_window_redirect_query,
|
||||
normalize_bj_datetime_storage,
|
||||
resolve_list_window,
|
||||
resolve_window,
|
||||
sql_list_time_field,
|
||||
utc_window_to_bj_sql_strings,
|
||||
)
|
||||
|
||||
@@ -5632,9 +5634,9 @@ def render_main_page(page="trade"):
|
||||
exchange_pnl_sync = sync_trade_records_from_exchange(conn) or {}
|
||||
except Exception as e:
|
||||
exchange_pnl_sync = {"ok": False, "reason": str(e)}
|
||||
tr_ts = sql_list_time_field("closed_at", "created_at", "opened_at")
|
||||
raw_records = conn.execute(
|
||||
"SELECT * FROM trade_records WHERE COALESCE(closed_at, created_at, opened_at) >= ? "
|
||||
"AND COALESCE(closed_at, created_at, opened_at) <= ? ORDER BY id DESC LIMIT 1000",
|
||||
f"SELECT * FROM trade_records WHERE {tr_ts} >= ? AND {tr_ts} <= ? ORDER BY id DESC LIMIT 1000",
|
||||
(start_bj, end_bj),
|
||||
).fetchall()
|
||||
records = [to_effective_trade_dict(r) for r in raw_records]
|
||||
@@ -6879,8 +6881,8 @@ def export_trade_records():
|
||||
"margin_capital,leverage,pnl_amount,hold_seconds,hold_minutes,planned_rr,actual_rr,risk_amount,"
|
||||
"opened_at,closed_at,result,miss_reason,entry_reason,reviewed_entry_reason,"
|
||||
"exchange_realized_pnl,exchange_opened_at,exchange_closed_at,created_at "
|
||||
"FROM trade_records WHERE COALESCE(closed_at, created_at, opened_at) >= ? "
|
||||
"AND COALESCE(closed_at, created_at, opened_at) <= ? ORDER BY id ASC",
|
||||
f"FROM trade_records WHERE {sql_list_time_field('closed_at', 'created_at', 'opened_at')} >= ? "
|
||||
f"AND {sql_list_time_field('closed_at', 'created_at', 'opened_at')} <= ? ORDER BY id ASC",
|
||||
(start_bj, end_bj),
|
||||
).fetchall()
|
||||
conn.close()
|
||||
@@ -7265,7 +7267,12 @@ def add_journal():
|
||||
new_trade_while_occupied, note, image)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
(
|
||||
entry_id, d.get("open_datetime"), d.get("close_datetime"), hold_duration, d.get("coin"), d.get("tf"),
|
||||
entry_id,
|
||||
normalize_bj_datetime_storage(d.get("open_datetime")),
|
||||
normalize_bj_datetime_storage(d.get("close_datetime")),
|
||||
hold_duration,
|
||||
d.get("coin"),
|
||||
d.get("tf"),
|
||||
d.get("pnl"), entry_reason_norm, exit_reason_stored, d.get("expect_rr"), real_rr_text,
|
||||
early_exit_raw, early_exit_reason_saved, early_exit_trigger, early_exit_note,
|
||||
None, None, None, mood_issues,
|
||||
@@ -7287,9 +7294,9 @@ def api_journals():
|
||||
win = _list_window_from_request()
|
||||
start_bj, end_bj = utc_window_to_bj_sql_strings(win["start_utc"], win["end_utc"], APP_TZ)
|
||||
conn = get_db()
|
||||
j_ts = sql_list_time_field("close_datetime", "created_at", "open_datetime")
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM journal_entries WHERE COALESCE(close_datetime, created_at, open_datetime) >= ? "
|
||||
"AND COALESCE(close_datetime, created_at, open_datetime) <= ? ORDER BY created_at DESC LIMIT 500",
|
||||
f"SELECT * FROM journal_entries WHERE {j_ts} >= ? AND {j_ts} <= ? ORDER BY created_at DESC LIMIT 500",
|
||||
(start_bj, end_bj),
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
@@ -40,8 +40,10 @@ from history_window_lib import (
|
||||
PRESET_UTC_LAST7D,
|
||||
PRESET_UTC_TODAY,
|
||||
list_window_redirect_query,
|
||||
normalize_bj_datetime_storage,
|
||||
resolve_list_window,
|
||||
resolve_window,
|
||||
sql_list_time_field,
|
||||
utc_window_to_bj_sql_strings,
|
||||
)
|
||||
|
||||
@@ -5213,8 +5215,8 @@ def render_main_page(page="trade"):
|
||||
pass
|
||||
if page in ("records", "plan_history"):
|
||||
raw_records = conn.execute(
|
||||
"SELECT * FROM trade_records WHERE COALESCE(closed_at, created_at, opened_at) >= ? "
|
||||
"AND COALESCE(closed_at, created_at, opened_at) <= ? ORDER BY id DESC LIMIT 2000",
|
||||
f"SELECT * FROM trade_records WHERE {sql_list_time_field('closed_at', 'created_at', 'opened_at')} >= ? "
|
||||
f"AND {sql_list_time_field('closed_at', 'created_at', 'opened_at')} <= ? ORDER BY id DESC LIMIT 2000",
|
||||
(start_bj, end_bj),
|
||||
).fetchall()
|
||||
else:
|
||||
@@ -6542,8 +6544,8 @@ def export_trade_records():
|
||||
"pnl_amount,hold_seconds,hold_minutes,opened_at,closed_at,result,miss_reason,"
|
||||
"entry_reason,reviewed_entry_reason,created_at,trend_plan_id,exchange_realized_pnl,"
|
||||
"exchange_opened_at,exchange_closed_at,exchange_sync_key FROM trade_records "
|
||||
"WHERE COALESCE(closed_at, created_at, opened_at) >= ? "
|
||||
"AND COALESCE(closed_at, created_at, opened_at) <= ? ORDER BY id ASC",
|
||||
f"WHERE {sql_list_time_field('closed_at', 'created_at', 'opened_at')} >= ? "
|
||||
f"AND {sql_list_time_field('closed_at', 'created_at', 'opened_at')} <= ? ORDER BY id ASC",
|
||||
(start_bj, end_bj),
|
||||
).fetchall()
|
||||
conn.close()
|
||||
@@ -6916,7 +6918,12 @@ def add_journal():
|
||||
new_trade_while_occupied, note, image)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
(
|
||||
entry_id, d.get("open_datetime"), d.get("close_datetime"), hold_duration, d.get("coin"), d.get("tf"),
|
||||
entry_id,
|
||||
normalize_bj_datetime_storage(d.get("open_datetime")),
|
||||
normalize_bj_datetime_storage(d.get("close_datetime")),
|
||||
hold_duration,
|
||||
d.get("coin"),
|
||||
d.get("tf"),
|
||||
d.get("pnl"), entry_reason_norm, exit_reason_stored, d.get("expect_rr"), real_rr_text,
|
||||
early_exit_raw, early_exit_reason_saved, early_exit_trigger, early_exit_note,
|
||||
None, None, None, mood_issues,
|
||||
@@ -6939,8 +6946,8 @@ def api_journals():
|
||||
start_bj, end_bj = utc_window_to_bj_sql_strings(win["start_utc"], win["end_utc"], APP_TZ)
|
||||
conn = get_db()
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM journal_entries WHERE COALESCE(close_datetime, created_at, open_datetime) >= ? "
|
||||
"AND COALESCE(close_datetime, created_at, open_datetime) <= ? ORDER BY created_at DESC LIMIT 500",
|
||||
f"SELECT * FROM journal_entries WHERE {sql_list_time_field('close_datetime', 'created_at', 'open_datetime')} >= ? "
|
||||
f"AND {sql_list_time_field('close_datetime', 'created_at', 'open_datetime')} <= ? ORDER BY created_at DESC LIMIT 500",
|
||||
(start_bj, end_bj),
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
@@ -59,8 +59,10 @@ from history_window_lib import (
|
||||
PRESET_UTC_LAST7D,
|
||||
PRESET_UTC_TODAY,
|
||||
list_window_redirect_query,
|
||||
normalize_bj_datetime_storage,
|
||||
resolve_list_window,
|
||||
resolve_window,
|
||||
sql_list_time_field,
|
||||
utc_window_to_bj_sql_strings,
|
||||
)
|
||||
|
||||
@@ -4166,8 +4168,8 @@ def render_main_page(page="trade"):
|
||||
for o in raw_order_list:
|
||||
order_list.append(enrich_order_item(row_to_dict(o), current_capital))
|
||||
raw_records = conn.execute(
|
||||
"SELECT * FROM trade_records WHERE COALESCE(closed_at, created_at, opened_at) >= ? "
|
||||
"AND COALESCE(closed_at, created_at, opened_at) <= ? ORDER BY id DESC LIMIT 1000",
|
||||
f"SELECT * FROM trade_records WHERE {sql_list_time_field('closed_at', 'created_at', 'opened_at')} >= ? "
|
||||
f"AND {sql_list_time_field('closed_at', 'created_at', 'opened_at')} <= ? ORDER BY id DESC LIMIT 1000",
|
||||
(start_bj, end_bj),
|
||||
).fetchall()
|
||||
records = [to_effective_trade_dict(r) for r in raw_records]
|
||||
@@ -5139,8 +5141,8 @@ def export_trade_records():
|
||||
"SELECT id,symbol,monitor_type,key_signal_type,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,"
|
||||
"margin_capital,leverage,pnl_amount,hold_seconds,hold_minutes,planned_rr,actual_rr,risk_amount,"
|
||||
"opened_at,closed_at,result,miss_reason,entry_reason,reviewed_entry_reason,created_at "
|
||||
"FROM trade_records WHERE COALESCE(closed_at, created_at, opened_at) >= ? "
|
||||
"AND COALESCE(closed_at, created_at, opened_at) <= ? ORDER BY id ASC",
|
||||
f"FROM trade_records WHERE {sql_list_time_field('closed_at', 'created_at', 'opened_at')} >= ? "
|
||||
f"AND {sql_list_time_field('closed_at', 'created_at', 'opened_at')} <= ? ORDER BY id ASC",
|
||||
(start_bj, end_bj),
|
||||
).fetchall()
|
||||
conn.close()
|
||||
@@ -5502,7 +5504,12 @@ def add_journal():
|
||||
new_trade_while_occupied, note, image)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
(
|
||||
entry_id, d.get("open_datetime"), d.get("close_datetime"), hold_duration, d.get("coin"), d.get("tf"),
|
||||
entry_id,
|
||||
normalize_bj_datetime_storage(d.get("open_datetime")),
|
||||
normalize_bj_datetime_storage(d.get("close_datetime")),
|
||||
hold_duration,
|
||||
d.get("coin"),
|
||||
d.get("tf"),
|
||||
d.get("pnl"), entry_reason_norm, exit_reason_stored, d.get("expect_rr"), real_rr_text,
|
||||
early_exit_raw, early_exit_reason_saved, early_exit_trigger, early_exit_note,
|
||||
None, None, None, mood_issues,
|
||||
@@ -5525,8 +5532,8 @@ def api_journals():
|
||||
start_bj, end_bj = utc_window_to_bj_sql_strings(win["start_utc"], win["end_utc"], APP_TZ)
|
||||
conn = get_db()
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM journal_entries WHERE COALESCE(close_datetime, created_at, open_datetime) >= ? "
|
||||
"AND COALESCE(close_datetime, created_at, open_datetime) <= ? ORDER BY created_at DESC LIMIT 500",
|
||||
f"SELECT * FROM journal_entries WHERE {sql_list_time_field('close_datetime', 'created_at', 'open_datetime')} >= ? "
|
||||
f"AND {sql_list_time_field('close_datetime', 'created_at', 'open_datetime')} <= ? ORDER BY created_at DESC LIMIT 500",
|
||||
(start_bj, end_bj),
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
+210
-210
@@ -1,210 +1,210 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
crypto_monitor 一键环境部署(Windows PowerShell)
|
||||
|
||||
.DESCRIPTION
|
||||
- 为各子项目创建 Python venv 并安装依赖
|
||||
- 从 .env.example 复制 .env(不覆盖已有)
|
||||
- 创建 static/images 等运行时目录
|
||||
- 可选安装 PM2(需已安装 Node.js)
|
||||
|
||||
.EXAMPLE
|
||||
.\deploy\setup_env.ps1
|
||||
.\deploy\setup_env.ps1 -Only binance,gate_bot
|
||||
.\deploy\setup_env.ps1 -SkipPm2
|
||||
#>
|
||||
param(
|
||||
[string]$Only = "all",
|
||||
[switch]$SkipPm2,
|
||||
[switch]$SkipEnvCopy,
|
||||
[switch]$RecreateVenv
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$DeployDir = $PSScriptRoot
|
||||
$RepoRoot = (Resolve-Path (Join-Path $DeployDir "..")).Path
|
||||
$ReqFile = Join-Path $RepoRoot "requirements.txt"
|
||||
$HubReqFile = Join-Path $RepoRoot "manual_trading_hub\requirements.txt"
|
||||
|
||||
$MonitorProjects = @(
|
||||
@{ Key = "binance"; Dir = "crypto_monitor_binance" },
|
||||
@{ Key = "gate"; Dir = "crypto_monitor_gate" },
|
||||
@{ Key = "gate_bot"; Dir = "crypto_monitor_gate_bot" },
|
||||
@{ Key = "okx"; Dir = "crypto_monitor_okx" }
|
||||
)
|
||||
$HubProject = @{ Key = "hub"; Dir = "manual_trading_hub" }
|
||||
|
||||
function Write-Step([string]$Msg) {
|
||||
Write-Host ""
|
||||
Write-Host "==> $Msg" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
function Test-Python310 {
|
||||
$py = Get-Command python -ErrorAction SilentlyContinue
|
||||
if (-not $py) {
|
||||
throw "未找到 python。请安装 Python 3.10+ 并加入 PATH:https://www.python.org/downloads/"
|
||||
}
|
||||
$verText = & python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
||||
$parts = $verText.Trim() -split "\."
|
||||
$major = [int]$parts[0]
|
||||
$minor = [int]$parts[1]
|
||||
if ($major -lt 3 -or ($major -eq 3 -and $minor -lt 10)) {
|
||||
throw "需要 Python 3.10+,当前: $verText"
|
||||
}
|
||||
Write-Host "Python: $(python --version 2>&1)" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
function Should-Include([string]$Key, [string[]]$Selected) {
|
||||
if ($Selected -contains "all") { return $true }
|
||||
return $Selected -contains $Key
|
||||
}
|
||||
|
||||
function Setup-MonitorProject([hashtable]$Proj) {
|
||||
$projPath = Join-Path $RepoRoot $Proj.Dir
|
||||
if (-not (Test-Path $projPath)) {
|
||||
Write-Host " 跳过(目录不存在): $($Proj.Dir)" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
Write-Step "$($Proj.Dir)"
|
||||
Push-Location $projPath
|
||||
try {
|
||||
$venvDir = Join-Path $projPath ".venv"
|
||||
$venvPy = Join-Path $venvDir "Scripts\python.exe"
|
||||
$venvPip = Join-Path $venvDir "Scripts\pip.exe"
|
||||
|
||||
if ($RecreateVenv -and (Test-Path $venvDir)) {
|
||||
Write-Host " 删除旧 venv ..."
|
||||
Remove-Item -Recurse -Force $venvDir
|
||||
}
|
||||
if (-not (Test-Path $venvPy)) {
|
||||
Write-Host " 创建 venv ..."
|
||||
& python -m venv .venv
|
||||
}
|
||||
|
||||
Write-Host " 升级 pip ..."
|
||||
& $venvPy -m pip install -U pip setuptools wheel -q
|
||||
|
||||
Write-Host " 安装依赖 (requirements.txt) ..."
|
||||
& $venvPip install -r $ReqFile -q
|
||||
|
||||
if (-not $SkipEnvCopy) {
|
||||
$envExample = Join-Path $projPath ".env.example"
|
||||
$envFile = Join-Path $projPath ".env"
|
||||
if ((Test-Path $envExample) -and -not (Test-Path $envFile)) {
|
||||
Copy-Item $envExample $envFile
|
||||
Write-Host " 已复制 .env.example -> .env" -ForegroundColor Green
|
||||
} elseif (Test-Path $envFile) {
|
||||
Write-Host " 保留已有 .env" -ForegroundColor DarkGray
|
||||
} else {
|
||||
Write-Host " 无 .env.example,请手动配置 .env" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
$staticDirs = @(
|
||||
"static\images",
|
||||
"static\images\order_charts"
|
||||
)
|
||||
foreach ($d in $staticDirs) {
|
||||
$full = Join-Path $projPath $d
|
||||
if (-not (Test-Path $full)) {
|
||||
New-Item -ItemType Directory -Path $full -Force | Out-Null
|
||||
}
|
||||
}
|
||||
Write-Host " 完成: $venvPy" -ForegroundColor Green
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function Setup-HubProject() {
|
||||
$projPath = Join-Path $RepoRoot $HubProject.Dir
|
||||
if (-not (Test-Path $projPath)) {
|
||||
Write-Host " 跳过 hub(目录不存在)" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
Write-Step $HubProject.Dir
|
||||
Push-Location $projPath
|
||||
try {
|
||||
$venvDir = Join-Path $projPath ".venv"
|
||||
$venvPy = Join-Path $venvDir "Scripts\python.exe"
|
||||
$venvPip = Join-Path $venvDir "Scripts\pip.exe"
|
||||
|
||||
if ($RecreateVenv -and (Test-Path $venvDir)) {
|
||||
Remove-Item -Recurse -Force $venvDir
|
||||
}
|
||||
if (-not (Test-Path $venvPy)) {
|
||||
& python -m venv .venv
|
||||
}
|
||||
& $venvPy -m pip install -U pip setuptools wheel -q
|
||||
if (Test-Path $HubReqFile) {
|
||||
& $venvPip install -r $HubReqFile -q
|
||||
}
|
||||
|
||||
if (-not $SkipEnvCopy) {
|
||||
$envExample = Join-Path $projPath ".env.example"
|
||||
$envFile = Join-Path $projPath ".env"
|
||||
if ((Test-Path $envExample) -and -not (Test-Path $envFile)) {
|
||||
Copy-Item $envExample $envFile
|
||||
Write-Host " 已复制 .env.example -> .env" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
Write-Host " 完成: $venvPy" -ForegroundColor Green
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Pm2IfNeeded() {
|
||||
if ($SkipPm2) { return }
|
||||
$node = Get-Command node -ErrorAction SilentlyContinue
|
||||
if (-not $node) {
|
||||
Write-Host "未检测到 Node.js,跳过 PM2。安装 Node 后执行: npm install -g pm2" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
Write-Step "PM2(可选进程托管)"
|
||||
$pm2 = Get-Command pm2 -ErrorAction SilentlyContinue
|
||||
if ($pm2) {
|
||||
Write-Host " PM2 已安装: $(pm2 -v)" -ForegroundColor Green
|
||||
return
|
||||
}
|
||||
Write-Host " 正在全局安装 pm2 ..."
|
||||
& npm install -g pm2
|
||||
Write-Host " PM2 安装完成。在各子目录执行: pm2 start ecosystem.config.cjs" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# --- main ---
|
||||
Write-Host "crypto_monitor 环境部署" -ForegroundColor White
|
||||
Write-Host "仓库根目录: $RepoRoot" -ForegroundColor DarkGray
|
||||
|
||||
if (-not (Test-Path $ReqFile)) {
|
||||
throw "缺少 $ReqFile"
|
||||
}
|
||||
|
||||
Test-Python310
|
||||
|
||||
$selected = ($Only -split "[,;\s]+" | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ })
|
||||
if (-not $selected -or $selected.Count -eq 0) { $selected = @("all") }
|
||||
|
||||
foreach ($p in $MonitorProjects) {
|
||||
if (Should-Include $p.Key $selected) {
|
||||
Setup-MonitorProject $p
|
||||
}
|
||||
}
|
||||
if (Should-Include $HubProject.Key $selected) {
|
||||
Setup-HubProject
|
||||
}
|
||||
|
||||
Install-Pm2IfNeeded
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "部署完成。下一步:" -ForegroundColor Green
|
||||
Write-Host " 1. 编辑各子目录 .env(API Key、密码、代理等)"
|
||||
Write-Host " 2. 启动示例(Binance):"
|
||||
Write-Host " cd crypto_monitor_binance"
|
||||
Write-Host " .\.venv\Scripts\activate"
|
||||
Write-Host " python app.py"
|
||||
Write-Host " 3. Linux 服务器可用: bash deploy/setup_env.sh"
|
||||
Write-Host ""
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
crypto_monitor 一键环境部署(Windows PowerShell)
|
||||
|
||||
.DESCRIPTION
|
||||
- 为各子项目创建 Python venv 并安装依赖
|
||||
- 从 .env.example 复制 .env(不覆盖已有)
|
||||
- 创建 static/images 等运行时目录
|
||||
- 可选安装 PM2(需已安装 Node.js)
|
||||
|
||||
.EXAMPLE
|
||||
.\deploy\setup_env.ps1
|
||||
.\deploy\setup_env.ps1 -Only binance,gate_bot
|
||||
.\deploy\setup_env.ps1 -SkipPm2
|
||||
#>
|
||||
param(
|
||||
[string]$Only = "all",
|
||||
[switch]$SkipPm2,
|
||||
[switch]$SkipEnvCopy,
|
||||
[switch]$RecreateVenv
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$DeployDir = $PSScriptRoot
|
||||
$RepoRoot = (Resolve-Path (Join-Path $DeployDir "..")).Path
|
||||
$ReqFile = Join-Path $RepoRoot "requirements.txt"
|
||||
$HubReqFile = Join-Path $RepoRoot "manual_trading_hub\requirements.txt"
|
||||
|
||||
$MonitorProjects = @(
|
||||
@{ Key = "binance"; Dir = "crypto_monitor_binance" },
|
||||
@{ Key = "gate"; Dir = "crypto_monitor_gate" },
|
||||
@{ Key = "gate_bot"; Dir = "crypto_monitor_gate_bot" },
|
||||
@{ Key = "okx"; Dir = "crypto_monitor_okx" }
|
||||
)
|
||||
$HubProject = @{ Key = "hub"; Dir = "manual_trading_hub" }
|
||||
|
||||
function Write-Step([string]$Msg) {
|
||||
Write-Host ""
|
||||
Write-Host "==> $Msg" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
function Test-Python310 {
|
||||
$py = Get-Command python -ErrorAction SilentlyContinue
|
||||
if (-not $py) {
|
||||
throw "未找到 python。请安装 Python 3.10+ 并加入 PATH:https://www.python.org/downloads/"
|
||||
}
|
||||
$verText = & python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
||||
$parts = $verText.Trim() -split "\."
|
||||
$major = [int]$parts[0]
|
||||
$minor = [int]$parts[1]
|
||||
if ($major -lt 3 -or ($major -eq 3 -and $minor -lt 10)) {
|
||||
throw "需要 Python 3.10+,当前: $verText"
|
||||
}
|
||||
Write-Host "Python: $(python --version 2>&1)" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
function Should-Include([string]$Key, [string[]]$Selected) {
|
||||
if ($Selected -contains "all") { return $true }
|
||||
return $Selected -contains $Key
|
||||
}
|
||||
|
||||
function Setup-MonitorProject([hashtable]$Proj) {
|
||||
$projPath = Join-Path $RepoRoot $Proj.Dir
|
||||
if (-not (Test-Path $projPath)) {
|
||||
Write-Host " 跳过(目录不存在): $($Proj.Dir)" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
Write-Step "$($Proj.Dir)"
|
||||
Push-Location $projPath
|
||||
try {
|
||||
$venvDir = Join-Path $projPath ".venv"
|
||||
$venvPy = Join-Path $venvDir "Scripts\python.exe"
|
||||
$venvPip = Join-Path $venvDir "Scripts\pip.exe"
|
||||
|
||||
if ($RecreateVenv -and (Test-Path $venvDir)) {
|
||||
Write-Host " 删除旧 venv ..."
|
||||
Remove-Item -Recurse -Force $venvDir
|
||||
}
|
||||
if (-not (Test-Path $venvPy)) {
|
||||
Write-Host " 创建 venv ..."
|
||||
& python -m venv .venv
|
||||
}
|
||||
|
||||
Write-Host " 升级 pip ..."
|
||||
& $venvPy -m pip install -U pip setuptools wheel -q
|
||||
|
||||
Write-Host " 安装依赖 (requirements.txt) ..."
|
||||
& $venvPip install -r $ReqFile -q
|
||||
|
||||
if (-not $SkipEnvCopy) {
|
||||
$envExample = Join-Path $projPath ".env.example"
|
||||
$envFile = Join-Path $projPath ".env"
|
||||
if ((Test-Path $envExample) -and -not (Test-Path $envFile)) {
|
||||
Copy-Item $envExample $envFile
|
||||
Write-Host " 已复制 .env.example -> .env" -ForegroundColor Green
|
||||
} elseif (Test-Path $envFile) {
|
||||
Write-Host " 保留已有 .env" -ForegroundColor DarkGray
|
||||
} else {
|
||||
Write-Host " 无 .env.example,请手动配置 .env" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
$staticDirs = @(
|
||||
"static\images",
|
||||
"static\images\order_charts"
|
||||
)
|
||||
foreach ($d in $staticDirs) {
|
||||
$full = Join-Path $projPath $d
|
||||
if (-not (Test-Path $full)) {
|
||||
New-Item -ItemType Directory -Path $full -Force | Out-Null
|
||||
}
|
||||
}
|
||||
Write-Host " 完成: $venvPy" -ForegroundColor Green
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function Setup-HubProject() {
|
||||
$projPath = Join-Path $RepoRoot $HubProject.Dir
|
||||
if (-not (Test-Path $projPath)) {
|
||||
Write-Host " 跳过 hub(目录不存在)" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
Write-Step $HubProject.Dir
|
||||
Push-Location $projPath
|
||||
try {
|
||||
$venvDir = Join-Path $projPath ".venv"
|
||||
$venvPy = Join-Path $venvDir "Scripts\python.exe"
|
||||
$venvPip = Join-Path $venvDir "Scripts\pip.exe"
|
||||
|
||||
if ($RecreateVenv -and (Test-Path $venvDir)) {
|
||||
Remove-Item -Recurse -Force $venvDir
|
||||
}
|
||||
if (-not (Test-Path $venvPy)) {
|
||||
& python -m venv .venv
|
||||
}
|
||||
& $venvPy -m pip install -U pip setuptools wheel -q
|
||||
if (Test-Path $HubReqFile) {
|
||||
& $venvPip install -r $HubReqFile -q
|
||||
}
|
||||
|
||||
if (-not $SkipEnvCopy) {
|
||||
$envExample = Join-Path $projPath ".env.example"
|
||||
$envFile = Join-Path $projPath ".env"
|
||||
if ((Test-Path $envExample) -and -not (Test-Path $envFile)) {
|
||||
Copy-Item $envExample $envFile
|
||||
Write-Host " 已复制 .env.example -> .env" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
Write-Host " 完成: $venvPy" -ForegroundColor Green
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Pm2IfNeeded() {
|
||||
if ($SkipPm2) { return }
|
||||
$node = Get-Command node -ErrorAction SilentlyContinue
|
||||
if (-not $node) {
|
||||
Write-Host "未检测到 Node.js,跳过 PM2。安装 Node 后执行: npm install -g pm2" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
Write-Step "PM2(可选进程托管)"
|
||||
$pm2 = Get-Command pm2 -ErrorAction SilentlyContinue
|
||||
if ($pm2) {
|
||||
Write-Host " PM2 已安装: $(pm2 -v)" -ForegroundColor Green
|
||||
return
|
||||
}
|
||||
Write-Host " 正在全局安装 pm2 ..."
|
||||
& npm install -g pm2
|
||||
Write-Host " PM2 安装完成。在各子目录执行: pm2 start ecosystem.config.cjs" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# --- main ---
|
||||
Write-Host "crypto_monitor 环境部署" -ForegroundColor White
|
||||
Write-Host "仓库根目录: $RepoRoot" -ForegroundColor DarkGray
|
||||
|
||||
if (-not (Test-Path $ReqFile)) {
|
||||
throw "缺少 $ReqFile"
|
||||
}
|
||||
|
||||
Test-Python310
|
||||
|
||||
$selected = ($Only -split "[,;\s]+" | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ })
|
||||
if (-not $selected -or $selected.Count -eq 0) { $selected = @("all") }
|
||||
|
||||
foreach ($p in $MonitorProjects) {
|
||||
if (Should-Include $p.Key $selected) {
|
||||
Setup-MonitorProject $p
|
||||
}
|
||||
}
|
||||
if (Should-Include $HubProject.Key $selected) {
|
||||
Setup-HubProject
|
||||
}
|
||||
|
||||
Install-Pm2IfNeeded
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "部署完成。下一步:" -ForegroundColor Green
|
||||
Write-Host " 1. 编辑各子目录 .env(API Key、密码、代理等)"
|
||||
Write-Host " 2. 启动示例(Binance):"
|
||||
Write-Host " cd crypto_monitor_binance"
|
||||
Write-Host " .\.venv\Scripts\activate"
|
||||
Write-Host " python app.py"
|
||||
Write-Host " 3. Linux 服务器可用: bash deploy/setup_env.sh"
|
||||
Write-Host ""
|
||||
|
||||
@@ -75,6 +75,28 @@ def utc_window_to_bj_sql_strings(start_utc, end_utc, app_tz):
|
||||
return start_bj, end_bj
|
||||
|
||||
|
||||
def normalize_bj_datetime_storage(raw):
|
||||
"""表单 datetime-local(含 T)入库前统一为 YYYY-MM-DD HH:MM:SS(北京时间)。"""
|
||||
s = (raw or "").strip().replace("T", " ").replace("Z", "").strip()
|
||||
if not s:
|
||||
return ""
|
||||
for fmt, n in (("%Y-%m-%d %H:%M:%S", 19), ("%Y-%m-%d %H:%M", 16), ("%Y-%m-%d", 10)):
|
||||
try:
|
||||
return datetime.strptime(s[:n], fmt).strftime("%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
continue
|
||||
return s
|
||||
|
||||
|
||||
def sql_list_time_field(*columns):
|
||||
"""
|
||||
SQLite 列表时间窗比较表达式。
|
||||
journal_entries 的 open/close 可能含 'T',直接与 bounds(空格格式)比会误判为超出上界。
|
||||
"""
|
||||
cols = ", ".join(columns)
|
||||
return f"REPLACE(COALESCE({cols}), 'T', ' ')"
|
||||
|
||||
|
||||
SESSION_KEY_LIST_WIN = "list_win_filter"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user