Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
361 changes: 361 additions & 0 deletions Research/最優造市參數計算器_ccxt.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,361 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 6,
"id": "be3eb8aa",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"正在從 bybit 獲取 BTC/USDT 的 1h K線數據...\n",
"📊 BTC/USDT 日化波動率估計值: 2.74%\n",
"\n",
"--- 🔬 最優造市參數計算結果 ---\n",
"asset : BTC/USDT\n",
"current_mid_price : 89029.4000 USDT\n",
"order_refresh_time_sec : 30.0000 秒\n",
"bid_spread : 0.0528 %\n",
"ask_spread : 0.0528 %\n",
"long_profit_taking_spread : 0.0528 %\n",
"short_profit_taking_spread : 0.0528 %\n",
"stop_loss_spread : 3.5057 %\n"
]
}
],
"source": [
"import ccxt\n",
"import pandas as pd\n",
"import numpy as np\n",
"from scipy.stats import norm\n",
"from datetime import datetime, timedelta, timezone\n",
"\n",
"# --- 常數定義 ---\n",
"DAYS_PER_YEAR = 365.25\n",
"SECONDS_PER_DAY = 24 * 3600\n",
"\n",
"# --- CCXT K 線資料抓取 ---\n",
"def get_kline_data(exchange_id: str, symbol: str, timeframe: str = \"1h\", limit: int = 720) -> pd.DataFrame:\n",
" \"\"\"\n",
" 使用 CCXT 從指定交易所取得歷史 K 線資料\n",
" \"\"\"\n",
" try:\n",
" exchange_class = getattr(ccxt, exchange_id)\n",
" exchange = exchange_class({\n",
" 'enableRateLimit': True,\n",
" 'options': {'defaultType': 'spot'} # 預設使用現貨,可根據需要調整\n",
" })\n",
" \n",
" # 檢查並載入市場數據以確保符號正確\n",
" exchange.load_markets()\n",
" \n",
" if symbol not in exchange.markets:\n",
" # 嘗試自動修正常見格式差異 (例如 gate 的 BTC_USDT -> BTC/USDT)\n",
" if '_' in symbol and '/' not in symbol:\n",
" symbol = symbol.replace('_', '/')\n",
" \n",
" if symbol not in exchange.markets:\n",
" raise ValueError(f\"交易所 {exchange_id} 不支持交易對 {symbol}\")\n",
"\n",
" # 獲取 K 線數據: [timestamp, open, high, low, close, volume]\n",
" ohlcv = exchange.fetch_ohlcv(symbol, timeframe, limit=limit)\n",
" \n",
" df = pd.DataFrame(ohlcv, columns=[\"timestamp\", \"open\", \"high\", \"low\", \"close\", \"volume\"])\n",
" df[\"timestamp\"] = pd.to_datetime(df[\"timestamp\"], unit=\"ms\", utc=True)\n",
" \n",
" # 依時間排序\n",
" df = df.sort_values(\"timestamp\").reset_index(drop=True)\n",
" \n",
" return df[[\"timestamp\", \"open\", \"high\", \"low\", \"close\"]]\n",
" \n",
" except Exception as e:\n",
" print(f\"獲取 K 線數據失敗: {e}\")\n",
" return pd.DataFrame()\n",
"\n",
"\n",
"# --- 核心策略計算 ---\n",
"def calculate_optimal_market_making_params(\n",
" asset: str,\n",
" mid_price: float,\n",
" daily_volatility_pct: float,\n",
" target_order_fill_prob: float,\n",
" order_refresh_time_sec: int,\n",
" stop_loss_risk_prob: float,\n",
" max_holding_time_days: float = 30.0,\n",
" profit_factor: float = 2.0\n",
") -> dict:\n",
" \"\"\"\n",
" 根據 GBM 波動率模型,計算最優造市參數\n",
" \"\"\"\n",
" daily_volatility = daily_volatility_pct / 100.0\n",
" annual_volatility = daily_volatility * np.sqrt(DAYS_PER_YEAR)\n",
" dt_order = order_refresh_time_sec / (DAYS_PER_YEAR * SECONDS_PER_DAY)\n",
" dt_loss = max_holding_time_days / DAYS_PER_YEAR\n",
"\n",
" # 基礎掛單價差\n",
" p_half_order = target_order_fill_prob / 2.0\n",
" Z_order = norm.ppf(p_half_order)\n",

Check warning on line 99 in Research/最優造市參數計算器_ccxt.ipynb

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this local variable "Z_order" to match the regular expression ^[_a-z][a-z0-9_]*$.

See more on https://sonarcloud.io/project/issues?id=skywalker0803r_Vedanta&issues=AZrvvK0GIYxZ4fmsK-3t&open=AZrvvK0GIYxZ4fmsK-3t&pullRequest=17
" base_spread_pct = (annual_volatility * np.sqrt(dt_order) * np.abs(Z_order)) * 100\n",
"\n",
" # 止盈與止損\n",
" profit_taking_spread_pct = base_spread_pct * profit_factor\n",
" p_half_loss = stop_loss_risk_prob / 2.0\n",
" Z_loss = norm.ppf(p_half_loss)\n",

Check warning on line 105 in Research/最優造市參數計算器_ccxt.ipynb

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this local variable "Z_loss" to match the regular expression ^[_a-z][a-z0-9_]*$.

See more on https://sonarcloud.io/project/issues?id=skywalker0803r_Vedanta&issues=AZrvvK0GIYxZ4fmsK-3s&open=AZrvvK0GIYxZ4fmsK-3s&pullRequest=17
" stop_loss_spread_pct = (annual_volatility * np.sqrt(dt_loss) * np.abs(Z_loss)) * 100\n",
"\n",
" return {\n",
" \"asset\": asset,\n",
" \"current_mid_price\": mid_price,\n",
" \"order_refresh_time_sec\": order_refresh_time_sec,\n",
" \"bid_spread\": round(base_spread_pct, 4),\n",
" \"ask_spread\": round(base_spread_pct, 4),\n",
" \"long_profit_taking_spread\": round(profit_taking_spread_pct, 4),\n",
" \"short_profit_taking_spread\": round(profit_taking_spread_pct, 4),\n",
" \"stop_loss_spread\": round(stop_loss_spread_pct, 4),\n",
" \"Z_score_order\": round(np.abs(Z_order), 4),\n",
" \"Z_score_stop_loss\": round(np.abs(Z_loss), 4)\n",
" }\n",
"\n",
"\n",
"# --- 主流程:自動從交易所取得波動率並計算策略 ---\n",
"def calculate_strategy(exchange_id: str, symbol: str, timeframe: str):\n",

Check failure on line 123 in Research/最優造市參數計算器_ccxt.ipynb

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 17 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=skywalker0803r_Vedanta&issues=AZrvvK0GIYxZ4fmsK-3u&open=AZrvvK0GIYxZ4fmsK-3u&pullRequest=17
" \"\"\"\n",
" 從指定交易所取得歷史資料,自動估算波動率並計算造市策略參數\n",
" \"\"\"\n",
" print(f\"正在從 {exchange_id} 獲取 {symbol} 的 {timeframe} K線數據...\")\n",
" df = get_kline_data(exchange_id, symbol, timeframe=timeframe, limit=720)\n",
"\n",
" if df.empty:\n",
" print(\"無法獲取數據,程序終止。\")\n",
" return\n",
"\n",
" # 計算對數報酬率\n",
" df[\"log_return\"] = np.log(df[\"close\"] / df[\"close\"].shift(1))\n",
" \n",
" # 計算標準差 (波動率)\n",
" period_vol = df[\"log_return\"].std()\n",
"\n",
" # 根據 timeframe 換算成日化波動率\n",
" # 假設 timeframe 格式為 '1m', '5m', '1h', '1d' 等\n",
" intervals_per_day = 24 # Default 1h\n",
" if timeframe == '1m':\n",
" intervals_per_day = 1440\n",
" elif timeframe == '5m':\n",
" intervals_per_day = 288\n",
" elif timeframe == '15m':\n",
" intervals_per_day = 96\n",
" elif timeframe == '30m':\n",
" intervals_per_day = 48\n",
" elif timeframe == '1h':\n",
" intervals_per_day = 24\n",
" elif timeframe == '4h':\n",
" intervals_per_day = 6\n",
" elif timeframe == '1d':\n",
" intervals_per_day = 1\n",
" \n",
" daily_vol = period_vol * np.sqrt(intervals_per_day)\n",
" print(f\"📊 {symbol} 日化波動率估計值: {daily_vol*100:.2f}%\")\n",
"\n",
" params = {\n",
" \"asset\": symbol,\n",
" \"mid_price\": df[\"close\"].iloc[-1],\n",
" \"daily_volatility_pct\": daily_vol * 100,\n",
" \"target_order_fill_prob\": 0.3,\n",
" \"order_refresh_time_sec\": 30,\n",
" \"stop_loss_risk_prob\": 0.2,\n",
" \"max_holding_time_days\": 1,\n",
" \"profit_factor\": 1\n",
" }\n",
"\n",
" result = calculate_optimal_market_making_params(**params)\n",
"\n",
" print(\"\\n--- 🔬 最優造市參數計算結果 ---\")\n",
" for key, value in result.items():\n",
" if \"_spread\" in key or \"time_sec\" in key:\n",
" unit = \"%\" if \"spread\" in key else \"秒\"\n",
" print(f\"{key:<30}: {value:>.4f} {unit}\")\n",
" elif key == \"current_mid_price\":\n",
" print(f\"{key:<30}: {value:>.4f} USDT\")\n",
" elif key == \"asset\":\n",
" print(f\"{key:<30}: {value}\")\n",
"\n",
" return result\n",
"\n",
"\n",
"# --- 主執行 ---\n",
"if __name__ == \"__main__\":\n",
" # 傳入參數\n",
" exchange_id = 'bybit' # 支援主流交易所 可參照以下輸出\n",
" symbol = 'BTC/USDT' # 需加上/USDT或/USDC等\n",
" timeframe = '1h' # 格式為 '1m', '5m', '1h', '1d' 等\n",
" calculate_strategy(exchange_id, symbol, timeframe)\n"
]
},
{
"cell_type": "markdown",
"id": "f4f9b29f",
"metadata": {},
"source": [
"### ccxt支援的交易所列表"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "f8773aea",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CCXT Version: 4.5.2\n",
"alpaca\n",
"apex\n",
"ascendex\n",
"bequant\n",
"bigone\n",
"binance\n",
"binancecoinm\n",
"binanceus\n",
"binanceusdm\n",
"bingx\n",
"bit2c\n",
"bitbank\n",
"bitbns\n",
"bitfinex\n",
"bitflyer\n",
"bitget\n",
"bithumb\n",
"bitmart\n",
"bitmex\n",
"bitopro\n",
"bitrue\n",
"bitso\n",
"bitstamp\n",
"bitteam\n",
"bittrade\n",
"bitvavo\n",
"blockchaincom\n",
"blofin\n",
"btcalpha\n",
"btcbox\n",
"btcmarkets\n",
"btcturk\n",
"bybit\n",
"cex\n",
"coinbase\n",
"coinbaseadvanced\n",
"coinbaseexchange\n",
"coinbaseinternational\n",
"coincatch\n",
"coincheck\n",
"coinex\n",
"coinmate\n",
"coinmetro\n",
"coinone\n",
"coinsph\n",
"coinspot\n",
"cryptocom\n",
"cryptomus\n",
"defx\n",
"delta\n",
"deribit\n",
"derive\n",
"digifinex\n",
"exmo\n",
"fmfwio\n",
"foxbit\n",
"gate\n",
"gateio\n",
"gemini\n",
"hashkey\n",
"hibachi\n",
"hitbtc\n",
"hollaex\n",
"htx\n",
"huobi\n",
"hyperliquid\n",
"independentreserve\n",
"indodax\n",
"kraken\n",
"krakenfutures\n",
"kucoin\n",
"kucoinfutures\n",
"latoken\n",
"lbank\n",
"luno\n",
"mercado\n",
"mexc\n",
"modetrade\n",
"myokx\n",
"ndax\n",
"novadax\n",
"oceanex\n",
"okcoin\n",
"okx\n",
"okxus\n",
"onetrading\n",
"oxfun\n",
"p2b\n",
"paradex\n",
"paymium\n",
"phemex\n",
"poloniex\n",
"probit\n",
"timex\n",
"tokocrypto\n",
"tradeogre\n",
"upbit\n",
"wavesexchange\n",
"whitebit\n",
"woo\n",
"woofipro\n",
"xt\n",
"yobit\n",
"zaif\n",
"zonda\n"
]
}
],
"source": [
"from pprint import pprint\n",
"import ccxt # noqa: E402\n",
"\n",
"print('CCXT Version:', ccxt.__version__)\n",
"\n",
"for exchange_id in ccxt.exchanges:\n",
" try:\n",
" exchange = getattr(ccxt, exchange_id)()\n",
" print(exchange_id)\n",
" # do what you want with this exchange\n",
" # pprint(dir(exchange))\n",
" except Exception as e:\n",
" print(e)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "vedanta",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}