diff --git a/config_examples/live.json b/config_examples/live.json index e7cd64fb..70588297 100644 --- a/config_examples/live.json +++ b/config_examples/live.json @@ -1,48 +1,84 @@ { - "exchange": { - "name": "okx", - "key": "cbda9fde-b9e3-4a2d-94f9-e5c3705dfb5c", - "secret": "CD3B7DB459CBBD540E0926E5C48150E1", - "password": "c^-d:g;P2S9?Q#^", - "enable_ws": false, - "ccxt_config": { - "enableRateLimit": true, - "rateLimit": 500, - "options": { - "defaultType": "spot" - } - }, - "ccxt_async_config": { - "enableRateLimit": true, - "rateLimit": 3000, - "timeout": 20000 - }, - "pair_whitelist": ["BTC/USDT", "TON/USDT", "DOT/USDT", "XRP/USDT", "OKB/USDT", "SOL/USDT", "DOGE/USDT", "WCT/USDT", "TRUMP/USDT", "SUI/USDT", "PEPE/USDT", "TRB/USDT", "MASK/USDT", "UNI/USDT", "KAITO/USDT"], - "pair_blacklist": [] + "exchange": { + "name": "okx", + "key": "cbda9fde-b9e3-4a2d-94f9-e5c3705dfb5c", + "secret": "CD3B7DB459CBBD540E0926E5C48150E1", + "password": "c^-d:g;P2S9?Q#^", + "enable_ws": false, + "ccxt_config": { + "enableRateLimit": true, + "rateLimit": 500, + "options": { + "defaultType": "spot" + } }, - "pairlists": [ - { - "method": "StaticPairList" - } + "ccxt_async_config": { + "enableRateLimit": true, + "rateLimit": 3000, + "timeout": 20000 + }, + "pair_whitelist": [ + "ADA/USDT", + "AVAX/USDT", + "BCH/USDT", + "BNB/USDT", + "BONK/USDT", + "BTC/USDT", + "CFX/USDT", + "CRV/USDT", + "DOGE/USDT", + "ETH/USDT", + "HBAR/USDT", + "ILV/USDT", + "LDO/USDT", + "LINK/USDT", + "LTC/USDT", + "MAGIC/USDT", + "ONDO/USDT", + "OP/USDT", + "PENGU/USDT", + "PEPE/USDT", + "SOL/USDT", + "SUI/USDT", + "TON/USDT", + "TRUMP/USDT", + "TRX/USDT", + "UNI/USDT", + "VINE/USDT", + "WIF/USDT", + "WLD/USDT", + "XRP/USDT" ], - "order_types": { - "entry": "market", - "exit": "market", - "stoploss": "limit", - "stoploss_on_exchange": false - }, - "order_time_in_force": { - "entry": "gtc", - "exit": "gtc" - }, - "entry_pricing": { - "price_side": "other", - "use_order_book": false, - "price_last_balance": 0.0 - }, - "exit_pricing": { - "price_side": "other", - "use_order_book": false - }, - "fee": 0.0008 + "pair_blacklist": [] + }, + "pairlists": [ + { + "method": "StaticPairList" + } + ], + "order_types": { + "entry": "market", + "exit": "market", + "stoploss": "limit", + "stoploss_on_exchange": false + }, + "order_time_in_force": { + "entry": "gtc", + "exit": "gtc" + }, + "entry_pricing": { + "price_side": "other", + "use_order_book": false, + "price_last_balance": 0.0 + }, + "exit_pricing": { + "price_side": "other", + "use_order_book": false + }, + "fee": 0.0008, + "api_server": { + "enabled": true, + "listen_ip_address": "0.0.0.0", + "listen_port": 8080 + } } diff --git a/tools/listpairs.sh b/tools/listpairs.sh new file mode 100755 index 00000000..784b0e04 --- /dev/null +++ b/tools/listpairs.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# OKX API 端点 +API_URL="https://www.okx.com/api/v5/market/tickers?instType=SPOT" + +# 发送请求并获取数据 +response=$(curl -s -X GET "$API_URL" -H "accept: application/json") + +# 检查请求是否成功 +if [ -z "$response" ]; then + echo "错误:无法获取 API 数据,请检查网络连接或 API 端点。" + exit 1 +fi + +# 解析 JSON 数据,只保留-USDT交易对,提取 instId 和 volCcy24h,按 volCcy24h 降序排序,取前 50 个 +pairs=$(echo "$response" | jq -r '.data | map(select(.instId | endswith("-USDT"))) | sort_by(.volCcy24h | tonumber) | reverse | .[0:50] | .[] | [.instId, .volCcy24h] | @csv') + +# 检查是否成功解析数据 +if [ -z "$pairs" ]; then + echo "错误:无法解析 API 返回的 JSON 数据或没有找到-USDT交易对,请检查响应格式。" + exit 1 +fi + +# 格式化输出 +echo "OKX 交易所当前交易量最高的 USDT 现货交易对(按 24 小时交易量降序排序):" +echo "排名,交易对,24小时交易量(USDT)" +echo "---------------------------------------------" +index=1 +echo "$pairs" | while IFS=, read -r instId volCcy24h; do + # 去除引号并格式化输出 + instId=$(echo "$instId" | tr -d '"') + volCcy24h=$(echo "$volCcy24h" | tr -d '"') + printf "%-5s %-15s %s\n" "$index" "$instId" "$volCcy24h" + ((index++)) +done + +exit 0 diff --git a/tools/live.sh b/tools/live.sh index a82da709..4e41af8f 100755 --- a/tools/live.sh +++ b/tools/live.sh @@ -2,170 +2,246 @@ set -e # 出错立即停止 -# 检查 .env 文件 -if [ ! -f ".env" ]; then - echo "⚠️ 本地缺少 .env 文件,请创建并配置。示例内容如下:" - echo "" - echo "STRATEGY_NAME=TheForceV7" - echo "CONFIG_FILE=basic.json" - echo "TEST_BRANCH=theforce-noai-test" - echo "DRYRUN_BRANCH=theforce-noai-dryrun" - echo "" - exit 1 -fi +### 函数定义区 ### -# 加载 .env 文件中的变量(位于项目根目录) -if [ -f ".env" ]; then - export $(grep -v '^#' .env | xargs) -else - echo "⚠️ 缺少 .env 文件,请创建并配置 TEST_BRANCH 和 DRYRUN_BRANCH" - exit 1 -fi +# 1. 从SQLite数据库获取当前开放交易的币对列表 +get_open_trades_pairs() { + local db_path="$1" + local pairs=() -# 获取当前分支名 -current_branch=$(git rev-parse --abbrev-ref HEAD) - -# 检查当前分支是否匹配 TEST_BRANCH 或 DRYRUN_BRANCH -if [[ "$current_branch" != "$TEST_BRANCH" && "$current_branch" != "$DRYRUN_BRANCH" ]]; then - echo "⚠️ 错误:当前分支 '$current_branch' 不符合环境变量配置要求。" - echo "请确保当前分支与 .env 文件中的 TEST_BRANCH 或 DRYRUN_BRANCH 配置一致。" - echo "TEST_BRANCH=$TEST_BRANCH" - echo "DRYRUN_BRANCH=$DRYRUN_BRANCH" - exit 1 -else - echo "✅ 当前分支 '$current_branch' 符合环境变量配置要求。" -fi - -# 如果当前分支名称包含 "dryrun",执行 git reset --hard -if [[ "$current_branch" == *"dryrun"* ]]; then - echo "当前分支为 '$current_branch',正在执行 git reset --hard 以确保代码干净..." - git reset --hard - if [ $? -ne 0 ]; then - echo "⚠️ 执行 git reset --hard 失败,请检查 Git 状态。" - exit 1 + if [ ! -f "$db_path" ]; then + echo "⚠️ 交易数据库文件 $db_path 不存在,开放交易对列表为空" >&2 + echo "" + return fi - echo "Git 工作区已清理。" -fi -# Function to extract the value of a parameter -get_param_value() { - local param="$1" - shift - while [[ $# -gt 0 ]]; do - case "$1" in - $param=*) - echo "${1#*=}" - return - ;; - $param) - # Check if the next argument exists and does not start with a dash - if [[ -n "$2" && "$2" != -* ]]; then - echo "$2" - return - else - echo "Error: Missing value for parameter $param" >&2 - exit 1 - fi - ;; - esac - shift - done - echo "" - return + local query_result=$(sqlite3 -header -csv "$db_path" "SELECT id, pair, open_date, amount, open_rate, max_rate, close_profit, stake_amount FROM trades WHERE is_open = 1 ORDER BY open_date DESC LIMIT 10;") + pairs=($(echo "$query_result" | awk -F ',' 'NR > 1 {print $2}' | tr -d '"')) + + echo "从数据库获取到开放交易对: ${pairs[*]}" >&2 + echo "${pairs[@]}" } -# Parse command line arguments -PAIRS_ARG=$(get_param_value "--pairs" "$@") -PAIR_REMOTE_LIST_URL=$(get_param_value "--pairRemoteList" "$@") +# 2. 从远程接口获取币对列表 +get_remote_pairs() { + local remote_url="$1" + local pairs=() -# 加载 .env 文件中的变量 -export $(grep -v '^#' .env | xargs) -# 设置默认值 -STRATEGY_NAME=${STRATEGY_NAME:-TheForceV7} -CONFIG_FILE=${CONFIG_FILE:-basic.json} -# 设置 PARAMS_NAME 为 STRATEGY_NAME 的小写形式 -PARAMS_NAME=$(echo "$STRATEGY_NAME" | tr '[:upper:]' '[:lower:]') + if [ -z "$remote_url" ]; then + echo "⚠️ 未提供远程币对列表URL,远程币对列表为空" >&2 + echo "" + return + fi -echo "Using strategy: $STRATEGY_NAME" -echo "Using config: $CONFIG_FILE" -echo "Using testBranch: $TEST_BRANCH" + echo "正在从远程URL获取币对列表: $remote_url" >&2 + local pairs_json=$(curl -s "$remote_url") -# 处理交易对参数:优先级为 --pairRemoteList > --pairs > 默认值 -if [ -n "$PAIR_REMOTE_LIST_URL" ]; then - # 从远程API获取交易对列表 - echo "Fetching pairs from remote URL: $PAIR_REMOTE_LIST_URL" - pairs_json=$(curl -s "$PAIR_REMOTE_LIST_URL") + if [ $? -ne 0 ] || [ -z "$pairs_json" ]; then + echo "⚠️ 远程币对列表获取失败,使用空列表" >&2 + echo "" + return + fi - # 检查API响应是否成功 - if [[ $? -ne 0 || -z "$pairs_json" ]]; then - echo "Error: Failed to fetch pairs from remote URL, using --pairs parameter or default" - if [ -n "$PAIRS_ARG" ]; then - PAIRS_FLAG="--pairs $PAIRS_ARG" - echo "Using pairs from --pairs parameter: $PAIRS_ARG" - else - PAIRS_FLAG="" - echo "No pairs parameter provided, using freqtrade default" - fi - else - # 解析JSON并提取交易对,将连字符替换为斜杠 - remote_pairs=$(echo "$pairs_json" | python3 -c " + local parsed_pairs=$(echo "$pairs_json" | python3 -c " import sys, json data = json.load(sys.stdin) pairs = [pair.replace('-', '/') for pair in data.get('pairlist', [])] print(' '.join(pairs) if pairs else '') ") - # 如果解析成功且有交易对 - if [[ -n "$remote_pairs" ]]; then - PAIRS_FLAG="--pairs $remote_pairs" - echo "Successfully fetched $(echo "$remote_pairs" | wc -w) pairs from remote URL" - echo "Pairs: $remote_pairs" - else - echo "Error: Failed to parse or empty pairlist from remote URL, using --pairs parameter or default" - if [ -n "$PAIRS_ARG" ]; then - PAIRS_FLAG="--pairs $PAIRS_ARG" - echo "Using pairs from --pairs parameter: $PAIRS_ARG" - else - PAIRS_FLAG="" - echo "No pairs parameter provided, using freqtrade default" - fi - fi + if [ -n "$parsed_pairs" ]; then + pairs=($parsed_pairs) + echo "从远程获取到币对: ${pairs[*]}" >&2 + else + echo "⚠️ 远程币对列表解析为空" >&2 fi -elif [ -n "$PAIRS_ARG" ]; then - # 使用 --pairs 参数提供的交易对 - PAIRS_FLAG="--pairs $PAIRS_ARG" - echo "Using pairs from --pairs parameter: $PAIRS_ARG" -else - # 没有提供任何交易对参数 - PAIRS_FLAG="" - echo "No pairs parameter provided, using freqtrade default" + + echo "${pairs[@]}" +} + +# 3. 合并并去重两个币对列表 +merge_and_deduplicate_pairs() { + local -a db_pairs=($1) + local -a remote_pairs=($2) + local -a merged=() + + merged=($(printf "%s\n" "${db_pairs[@]}" "${remote_pairs[@]}" | sort -u | tr '\n' ' ')) + + echo "合并去重后的币对列表: ${merged[*]}" >&2 + echo "${merged[@]}" +} + +# 4. 验证JSON文件格式是否有效 +validate_json_file() { + local json_path="$1" + + if ! command -v jq &>/dev/null; then + echo "❌ 未安装jq工具,请先安装jq" >&2 + exit 1 + fi + + if [ ! -f "$json_path" ]; then + echo "❌ JSON配置文件 $json_path 不存在" >&2 + exit 1 + fi + + if ! jq . "$json_path" >/dev/null 2>&1; then + echo "❌ JSON文件格式错误:$json_path" >&2 + exit 1 + fi +} + +# 5. 更新live.json中的pair_whitelist +update_live_json_pair_whitelist() { + local json_path="$1" + local -a pairlist=($2) + + validate_json_file "$json_path" + + local json_array=$(printf '%s\n' "${pairlist[@]}" | jq -R . | jq -s .) + + echo "正在更新 $json_path 中的exchange.pair_whitelist..." >&2 + jq --argjson pairs "$json_array" '.exchange.pair_whitelist = $pairs' "$json_path" >"$json_path.tmp" && mv "$json_path.tmp" "$json_path" + + if [ $? -eq 0 ]; then + echo "✅ 成功更新 $json_path,新的pair_whitelist包含 ${#pairlist[@]} 个币对" >&2 + else + echo "❌ 更新 $json_path 失败" >&2 + exit 1 + fi +} + +# 6. 停止并移除指定名称的容器 +remove_existing_container() { + local container_name="$1" + + if [ -z "$container_name" ]; then + echo "⚠️ 容器名称为空,跳过移除操作" >&2 + return + fi + + # 检查容器是否存在 + if docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then + echo "发现同名容器 $container_name,正在停止并移除..." >&2 + # 停止容器 + if ! docker stop "$container_name" >&2; then + echo "⚠️ 停止容器 $container_name 失败,尝试强制移除..." >&2 + fi + # 移除容器 + if docker rm "$container_name" >&2; then + echo "✅ 已成功移除容器 $container_name" >&2 + else + echo "❌ 移除容器 $container_name 失败,请手动处理" >&2 + exit 1 + fi + else + echo "未发现同名容器 $container_name,无需移除" >&2 + fi +} + +### 主逻辑区 ### + +# 检查 .env 文件 +if [ ! -f ".env" ]; then + echo "⚠️ 本地缺少 .env 文件,请创建并配置" >&2 + exit 1 fi -cd ../ -source .venv/bin/activate -rm -rf user_data/models/* -rm -rf ./freqtrade/user_data/data/backtest_results/* -rm -fr ./user_data/dryrun_results/* -rm result/* || true +# 加载 .env 变量 +export $(grep -v '^#' .env | xargs) -hyperopt_config="${STRATEGY_NAME%.py}.json" -docker rm $(docker ps -aq) -f || true +# 检查必要环境变量 +if [ -z "$TEST_BRANCH" ] || [ -z "$DRYRUN_BRANCH" ]; then + echo "⚠️ .env 文件缺少 TEST_BRANCH 或 DRYRUN_BRANCH 配置" >&2 + exit 1 +fi + +# 检查当前分支 +current_branch=$(git rev-parse --abbrev-ref HEAD) +if [[ "$current_branch" != "$TEST_BRANCH" && "$current_branch" != "$DRYRUN_BRANCH" ]]; then + echo "⚠️ 错误:当前分支 '$current_branch' 不符合配置要求" >&2 + exit 1 +else + echo "✅ 当前分支 '$current_branch' 符合配置要求" >&2 +fi + +# dryrun分支清理 +if [[ "$current_branch" == *"dryrun"* ]]; then + echo "当前为dryrun分支,执行git reset --hard清理工作区..." >&2 + git reset --hard || { + echo "⚠️ git reset失败" + exit 1 + } + echo "✅ Git工作区已清理" >&2 +fi + +# 设置默认远程币对列表URL,支持命令行参数覆盖 +DEFAULT_PAIR_REMOTE_URL="http://pairlist.xl.home/api/pairlist?mute=true&count=30" +PAIR_REMOTE_LIST_URL="$DEFAULT_PAIR_REMOTE_URL" + +# 解析命令行参数:如果提供则覆盖默认值 +while [[ $# -gt 0 ]]; do + case "$1" in + --pairRemoteList=*) + PAIR_REMOTE_LIST_URL="${1#*=}" + shift + ;; + --pairRemoteList) + if [[ -n "$2" && "$2" != -* ]]; then + PAIR_REMOTE_LIST_URL="$2" + shift 2 + else + echo "错误:--pairRemoteList需要指定值" >&2 + exit 1 + fi + ;; + *) + shift + ;; + esac +done + +# 加载策略配置 +STRATEGY_NAME=${STRATEGY_NAME:-TheForceV7} +CONFIG_FILE=${CONFIG_FILE:-basic.json} +PARAMS_NAME=$(echo "$STRATEGY_NAME" | tr '[:upper:]' '[:lower:]') + +echo "使用策略: $STRATEGY_NAME" >&2 +echo "使用配置: $CONFIG_FILE" >&2 +echo "测试分支: $TEST_BRANCH" >&2 +echo "远程币对列表URL: $PAIR_REMOTE_LIST_URL" >&2 # 显示当前使用的URL + +### 核心:处理币对列表 ### + +# 1. 获取数据库币对(使用绝对路径) +db_path="/home/ubuntu/freqtrade/user_data/tradesv3.sqlite" +db_pairs=$(get_open_trades_pairs "$db_path") + +# 2. 获取远程币对 +remote_pairs=$(get_remote_pairs "$PAIR_REMOTE_LIST_URL") + +# 3. 合并去重 +merged_pairs=$(merge_and_deduplicate_pairs "$db_pairs" "$remote_pairs") + +# 4. 更新配置文件 +update_live_json_pair_whitelist "../config_examples/live.json" "$merged_pairs" + +### 启动容器 ### -# 获取当前 Git Commit 的前 8 位 GIT_COMMIT_SHORT=$(git rev-parse HEAD | cut -c 1-8) -echo "docker-compose run -d --rm --name freqtrade-dryrun-${GIT_COMMIT_SHORT} -p 8080:8080 freqtrade trade \ - --logfile /freqtrade/user_data/logs/freqtrade.log \ - --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite \ - --freqaimodel LightGBMRegressorMultiTarget \ - --fee 0.0008 - --config /freqtrade/config_examples/$CONFIG_FILE \ - --config /freqtrade/templates/${PARAMS_NAME}.json \ - --config /freqtrade/config_examples/live.json \ - --strategy $STRATEGY_NAME \ - --strategy-path /freqtrade/templates" +CONTAINER_NAME="freqtrade-dryrun-${GIT_COMMIT_SHORT}" -docker-compose run -d --rm --name freqtrade-dryrun-${GIT_COMMIT_SHORT} -p 8080:8080 freqtrade trade \ - $PAIRS_FLAG \ +echo "准备启动容器: $CONTAINER_NAME" >&2 + +# 移除已存在的同名容器 +remove_existing_container "$CONTAINER_NAME" + +# 启动新容器 +echo "启动容器: $CONTAINER_NAME" >&2 +docker-compose run -d --rm \ + --name "$CONTAINER_NAME" \ + -p 8080:8080 \ + freqtrade trade \ --logfile /freqtrade/user_data/logs/freqtrade.log \ --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite \ --freqaimodel LightGBMRegressorMultiTarget \ @@ -176,3 +252,10 @@ docker-compose run -d --rm --name freqtrade-dryrun-${GIT_COMMIT_SHORT} -p 8080:8 --enable-protections \ --strategy $STRATEGY_NAME \ --strategy-path /freqtrade/templates + +if [ $? -eq 0 ]; then + echo "✅ 容器 $CONTAINER_NAME 启动完成" >&2 +else + echo "❌ 容器启动失败" >&2 + exit 1 +fi diff --git a/tools/showtrades.sh b/tools/showtrades.sh new file mode 100755 index 00000000..8557915e --- /dev/null +++ b/tools/showtrades.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e # 出错立即停止 + +# 获取当前 Git Commit 的前 8 位 +GIT_COMMIT_SHORT=$(git rev-parse HEAD | cut -c 1-8) +docker-compose run --rm --name freqtrade-showtrades-${GIT_COMMIT_SHORT} freqtrade show-trades \ + --print-json \ + --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite \ diff --git a/tools/trades.sh b/tools/trades.sh new file mode 100755 index 00000000..4d9d1d6b --- /dev/null +++ b/tools/trades.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +sqlite3 -header -csv ~/freqtrade/user_data/tradesv3.sqlite "SELECT id, pair, open_date, amount, open_rate, max_rate, close_profit, stake_amount FROM trades WHERE is_open = 0 ORDER BY open_date DESC LIMIT 10;"