Skip to content

Commit c161727

Browse files
committed
Merge branch 'feat_pkcs8_cli' into 'master'
feat: v3.5.8 — support file path for props_path, add scanner CLI command, fix asset rendering See merge request server/openapi/openapi-python-sdk!275
2 parents a7ee07e + 5daf37b commit c161727

10 files changed

Lines changed: 430 additions & 16 deletions

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 3.5.8 (2026-04-24)
2+
### New
3+
- CLI 新增 `tigeropen quote scanner` 选股命令,支持 `--filter`(数值/累计/财务/标签筛选,预设 `gainers`/`losers`)、`--sort``--sort-dir``--limit` 参数
4+
- 新增 Windows PowerShell 一键安装脚本 `install.ps1``irm .../install.ps1 | iex`);`install.sh` 在 MINGW/MSYS/Cygwin 环境下增加 PowerShell 安装提示
5+
### Fix
6+
- 修复 `_get_props_path()` 传入完整文件路径时文件名被替换为 `tiger_openapi_config.properties` 的问题,现在支持任意文件名
7+
- 修复 `account assets` 命令渲染 `PortfolioAccount` 对象时报错的问题
8+
19
## 3.5.7 (2026-03-25)
210
### Fix
311
- 修复 pyproject.toml 中 build-backend 使用 `setuptools.backends._legacy:_Backend` 导致旧版 pip/setuptools 安装失败的问题,改为标准的 `setuptools.build_meta`

install.ps1

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Tiger Open API Python SDK — Windows Installer (PowerShell)
2+
#
3+
# Usage:
4+
# irm https://raw.githubusercontent.com/tigerfintech/openapi-python-sdk/master/install.ps1 | iex
5+
#
6+
# Options (via environment variables):
7+
# $env:TIGEROPEN_INSTALL_METHOD = "uv" -- force install method (uv|pipx|pip)
8+
# $env:TIGEROPEN_NO_MODIFY_PATH = "1" -- skip PATH modification
9+
10+
$ErrorActionPreference = "Stop"
11+
12+
# ─── Colors ──────────────────────────────────────────────────────────────────
13+
14+
function Write-Info { param($msg) Write-Host "info: $msg" -ForegroundColor Green }
15+
function Write-Warn { param($msg) Write-Host "warn: $msg" -ForegroundColor Yellow }
16+
function Write-Err { param($msg) Write-Host "error: $msg" -ForegroundColor Red; exit 1 }
17+
18+
function Has-Command { param($cmd) return [bool](Get-Command $cmd -ErrorAction SilentlyContinue) }
19+
20+
# ─── Banner ──────────────────────────────────────────────────────────────────
21+
22+
Write-Host ""
23+
Write-Host " Tiger Open API Python SDK Installer" -ForegroundColor Cyan
24+
Write-Host " ─────────────────────────────────────"
25+
Write-Host ""
26+
27+
# ─── Python Detection ────────────────────────────────────────────────────────
28+
29+
$PYTHON = $null
30+
foreach ($candidate in @("python", "python3", "py")) {
31+
if (Has-Command $candidate) {
32+
$PYTHON = $candidate
33+
break
34+
}
35+
}
36+
37+
if (-not $PYTHON) {
38+
Write-Err "Python not found. Install Python 3.8+ first:`n https://www.python.org/downloads/`n Or: winget install Python.Python.3"
39+
}
40+
41+
$PY_VERSION = & $PYTHON -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>$null
42+
$PY_MAJOR = & $PYTHON -c "import sys; print(sys.version_info.major)" 2>$null
43+
$PY_MINOR = & $PYTHON -c "import sys; print(sys.version_info.minor)" 2>$null
44+
45+
if (-not $PY_VERSION) { Write-Err "Could not determine Python version" }
46+
if ([int]$PY_MAJOR -lt 3 -or ([int]$PY_MAJOR -eq 3 -and [int]$PY_MINOR -lt 8)) {
47+
Write-Err "Python 3.8+ required, found $PY_VERSION"
48+
}
49+
50+
Write-Info "Found Python $PY_VERSION ($PYTHON)"
51+
52+
# ─── Choose Install Method ───────────────────────────────────────────────────
53+
54+
$METHOD = $env:TIGEROPEN_INSTALL_METHOD
55+
56+
if (-not $METHOD) {
57+
if (Has-Command "uv") { $METHOD = "uv" }
58+
elseif (Has-Command "pipx") { $METHOD = "pipx" }
59+
else { $METHOD = "pip" }
60+
}
61+
62+
Write-Info "Install method: $METHOD"
63+
64+
# ─── Install ─────────────────────────────────────────────────────────────────
65+
66+
switch ($METHOD) {
67+
"uv" {
68+
if (-not (Has-Command "uv")) { Write-Err "uv not found. Install with: irm https://astral.sh/uv/install.ps1 | iex" }
69+
Write-Info "Installing tigeropen via uv..."
70+
& uv pip install tigeropen --upgrade
71+
}
72+
"pipx" {
73+
if (-not (Has-Command "pipx")) { Write-Err "pipx not found. Install with: pip install pipx" }
74+
Write-Info "Installing tigeropen via pipx..."
75+
& pipx install tigeropen --force
76+
}
77+
"pip" {
78+
Write-Info "Installing tigeropen via pip..."
79+
& $PYTHON -m pip install tigeropen --upgrade
80+
}
81+
default {
82+
Write-Err "Unknown install method: $METHOD (use uv, pipx, or pip)"
83+
}
84+
}
85+
86+
# ─── Verify Installation ─────────────────────────────────────────────────────
87+
88+
$SDK_VERSION = & $PYTHON -c "from tigeropen import __VERSION__; print(__VERSION__)" 2>$null
89+
if (-not $SDK_VERSION) {
90+
Write-Err "Installation failed — could not import tigeropen"
91+
}
92+
93+
Write-Info "tigeropen v$SDK_VERSION installed successfully"
94+
95+
# ─── PATH Setup ──────────────────────────────────────────────────────────────
96+
97+
if ($env:TIGEROPEN_NO_MODIFY_PATH -ne "1") {
98+
$TIGEROPEN_BIN = Get-Command "tigeropen" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source
99+
100+
if (-not $TIGEROPEN_BIN) {
101+
# Check common pip/uv install locations on Windows
102+
$candidates = @(
103+
"$env:APPDATA\Python\Scripts\tigeropen.exe",
104+
"$env:LOCALAPPDATA\Programs\Python\Python$($PY_MAJOR)$($PY_MINOR)\Scripts\tigeropen.exe",
105+
"$env:LOCALAPPDATA\Programs\Python\Python$($PY_MAJOR)$($PY_MINOR.PadLeft(2,'0'))\Scripts\tigeropen.exe"
106+
)
107+
foreach ($path in $candidates) {
108+
if (Test-Path $path) {
109+
$TIGEROPEN_BIN = $path
110+
break
111+
}
112+
}
113+
}
114+
115+
if ($TIGEROPEN_BIN) {
116+
Write-Info "CLI installed at: $TIGEROPEN_BIN"
117+
$BIN_DIR = Split-Path $TIGEROPEN_BIN
118+
119+
# Check if already in PATH
120+
$currentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
121+
if ($currentPath -notlike "*$BIN_DIR*") {
122+
[Environment]::SetEnvironmentVariable("PATH", "$BIN_DIR;$currentPath", "User")
123+
Write-Info "Added $BIN_DIR to user PATH"
124+
Write-Warn "Restart your terminal (or open a new PowerShell window) for PATH changes to take effect"
125+
}
126+
} else {
127+
Write-Warn "tigeropen installed but CLI not found on PATH"
128+
Write-Warn "You may need to add pip's Scripts directory to your PATH manually"
129+
Write-Warn "Run: & $PYTHON -m site --user-site (then look for the Scripts sibling dir)"
130+
}
131+
}
132+
133+
# ─── Success Message ─────────────────────────────────────────────────────────
134+
135+
Write-Host ""
136+
Write-Host " Installation complete!" -ForegroundColor Green
137+
Write-Host ""
138+
Write-Host " Getting started:"
139+
Write-Host ""
140+
Write-Host " # Set up your API credentials" -ForegroundColor Cyan
141+
Write-Host " tigeropen config init"
142+
Write-Host ""
143+
Write-Host " # Or set environment variables" -ForegroundColor Cyan
144+
Write-Host " `$env:TIGEROPEN_TIGER_ID = 'your_tiger_id'"
145+
Write-Host " `$env:TIGEROPEN_PRIVATE_KEY = 'your_private_key'"
146+
Write-Host " `$env:TIGEROPEN_ACCOUNT = 'your_account'"
147+
Write-Host ""
148+
Write-Host " # Query market data" -ForegroundColor Cyan
149+
Write-Host " tigeropen quote briefs AAPL TSLA"
150+
Write-Host " tigeropen quote bars AAPL --period day --limit 10"
151+
Write-Host ""
152+
Write-Host " # Manage orders" -ForegroundColor Cyan
153+
Write-Host " tigeropen trade order list"
154+
Write-Host " tigeropen account assets"
155+
Write-Host ""
156+
Write-Host " Documentation: https://docs.itigerup.com/docs/" -ForegroundColor Cyan
157+
Write-Host " GitHub: https://github.com/tigerfintech/openapi-python-sdk" -ForegroundColor Cyan
158+
Write-Host ""

install.sh

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,18 @@ OS=$(uname -s)
5353
case "$OS" in
5454
Linux*) OS="linux" ;;
5555
Darwin*) OS="macos" ;;
56-
MINGW*|MSYS*|CYGWIN*) OS="windows" ;;
56+
MINGW*|MSYS*|CYGWIN*)
57+
OS="windows"
58+
printf '%s\n' ""
59+
printf '%s\n' "${yellow} Windows detected.${reset}"
60+
printf '%s\n' " This shell script works under Git Bash / MSYS2 / Cygwin, but"
61+
printf '%s\n' " the native Windows installer (PowerShell) is recommended:"
62+
printf '%s\n' ""
63+
printf '%s\n' " ${cyan}irm https://raw.githubusercontent.com/tigerfintech/openapi-python-sdk/master/install.ps1 | iex${reset}"
64+
printf '%s\n' ""
65+
printf '%s\n' " Continuing with shell installer..."
66+
printf '%s\n' ""
67+
;;
5768
*) warn "unrecognized OS: $OS — proceeding anyway" ;;
5869
esac
5970

@@ -142,8 +153,10 @@ if [ -z "$TIGEROPEN_BIN" ]; then
142153
"$HOME/.local/bin" \
143154
"$HOME/Library/Python/${PY_VERSION}/bin" \
144155
"$HOME/.local/share/pipx/venvs/tigeropen/bin" \
156+
"$APPDATA/Python/Scripts" \
157+
"$LOCALAPPDATA/Programs/Python/Python${PY_MAJOR}${PY_MINOR}/Scripts" \
145158
; do
146-
if [ -x "$dir/tigeropen" ]; then
159+
if [ -x "$dir/tigeropen" ] || [ -x "$dir/tigeropen.exe" ]; then
147160
TIGEROPEN_BIN="$dir/tigeropen"
148161
break
149162
fi

tests/test_option_util.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -637,10 +637,8 @@ def test_real_option_metrics(self):
637637
from tigeropen.tiger_open_config import TigerOpenClientConfig
638638
import os
639639

640-
# This would require actual configuration
641-
current_dir = os.path.dirname(__file__)
642640
client_config = TigerOpenClientConfig(
643-
props_path=os.path.join(current_dir, ".config/prod_2015xxxx/")
641+
props_path=os.path.expanduser("~/.tigeropen/")
644642
)
645643
quote_client = QuoteClient(client_config)
646644
option_util = OptionUtil(quote_client)

tests/test_quote_client.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@ class TestQuoteClient(unittest.TestCase):
3030

3131
def setUp(self):
3232
self.is_mock = True
33-
current_dir = os.path.dirname(__file__)
3433
self.client_config = TigerOpenClientConfig(
35-
props_path=os.path.join(current_dir, ".config/prod_xxxx/"))
34+
props_path=os.path.expanduser("~/.tigeropen/"))
3635
self.client: QuoteClient = QuoteClient(self.client_config,
3736
logger=logger,
3837
is_grab_permission=False)

tests/test_trade_client.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@ class TestTradeClient(unittest.TestCase):
2626

2727
def setUp(self):
2828
self.is_mock = False
29-
current_dir = os.path.dirname(__file__)
3029
self.client_config = TigerOpenClientConfig(
31-
props_path=os.path.join(current_dir, ".config/prod_2015xxxx/"))
30+
props_path=os.path.expanduser("~/.tigeropen/"))
3231
self.client: TradeClient = TradeClient(self.client_config,
3332
logger=logger)
3433
self.origin_do_request = web_utils.do_request

tigeropen/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
55
@author: gaoan
66
"""
7-
__VERSION__ = '3.5.7'
7+
__VERSION__ = '3.5.8'

tigeropen/cli/account_cmd.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,28 @@ def account_info(ctx):
2727
click.echo('No account info available.')
2828

2929

30+
def _segment_to_dict(segment):
31+
"""Serialize a Segment object to a plain dict."""
32+
d = {k: v for k, v in segment.__dict__.items() if not k.startswith('_')}
33+
d['currency_assets'] = {
34+
currency: {k: v for k, v in ca.__dict__.items() if not k.startswith('_')}
35+
for currency, ca in segment.currency_assets.items()
36+
}
37+
return d
38+
39+
40+
def _portfolio_account_to_dict(account):
41+
"""Serialize a PortfolioAccount object to a plain dict."""
42+
return {
43+
'account': account.account,
44+
'update_timestamp': account.update_timestamp,
45+
'segments': {
46+
key: _segment_to_dict(seg)
47+
for key, seg in account._segments.items()
48+
},
49+
}
50+
51+
3052
@account.command('assets')
3153
@click.option('--currency', default=None, help='Currency filter (USD, HKD, etc.).')
3254
@click.pass_context
@@ -38,12 +60,7 @@ def account_assets(ctx, currency):
3860
kwargs['base_currency'] = currency
3961
result = client.get_prime_assets(**kwargs)
4062
if not is_empty(result):
41-
if hasattr(result, 'to_dict'):
42-
render(result.to_dict(), ctx.obj['format'])
43-
elif hasattr(result, '__dict__'):
44-
render(result.__dict__, ctx.obj['format'])
45-
else:
46-
render(result, ctx.obj['format'])
63+
render(_portfolio_account_to_dict(result), ctx.obj['format'])
4764
else:
4865
click.echo('No asset data available.')
4966

0 commit comments

Comments
 (0)