Skip to content

Commit e27b7fb

Browse files
v4.0.4
Signed-off-by: Dinger <quantdinger@gmail.com>
1 parent edd91af commit e27b7fb

97 files changed

Lines changed: 7550 additions & 6639 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CONTRIBUTING.md‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,19 @@ For local iteration without publishing, clone the Vue repo into `./QuantDinger-V
121121

122122
---
123123

124+
## Code Organization
125+
126+
Read [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md), [`docs/EXTENSION_GUIDE.md`](docs/EXTENSION_GUIDE.md), and [`docs/MODULE_BOUNDARIES.md`](docs/MODULE_BOUNDARIES.md) before larger backend changes.
127+
128+
- Keep routes thin: validate input, call a service, return JSON.
129+
- Put exchange, broker, market data, and strategy behavior behind small services or adapters.
130+
- Avoid growing legacy hotspot files when a focused sibling module can hold new behavior.
131+
- Keep code comments, docstrings, and log messages in English unless a user-facing translation or external provider field requires another language.
132+
- Prefer simple, readable code over clever abstractions.
133+
- Ask for clarification before changing unclear product behavior.
134+
135+
---
136+
124137
## 🌿 Branching & Pull Requests
125138

126139
### Branch naming
@@ -205,4 +218,4 @@ What is guaranteed:
205218
- your name will be attached to it
206219
- your contribution will remain yours
207220

208-
Build carefully. Build openly. Build things that last.
221+
Build carefully. Build openly. Build things that last.

‎README.md‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -424,12 +424,12 @@ The backend entrypoint auto-generates a random `SECRET_KEY` on first start and a
424424

425425
```env
426426
# Common case: lockstep both sides to one tag
427-
IMAGE_TAG=4.0.3
427+
IMAGE_TAG=4.0.4
428428
429429
# Advanced (opt-in): decouple sides. Either var alone overrides only
430430
# that side; the other still follows IMAGE_TAG.
431-
# BACKEND_TAG=4.0.3
432-
# FRONTEND_TAG=4.0.3
431+
# BACKEND_TAG=4.0.4
432+
# FRONTEND_TAG=4.0.4
433433
# MOBILE_TAG=4.0.3
434434
435435
# BACKEND_IMAGE=ghcr.io/<your-fork>/quantdinger-backend # optional, for forks
@@ -441,7 +441,7 @@ Tag resolution: `BACKEND_TAG` / `FRONTEND_TAG` / `MOBILE_TAG` → `IMAGE_TAG`
441441

442442
#### Version stamping
443443

444-
Published backend images are stamped from the Git release tag automatically. A `v4.0.3` tag becomes `APP_VERSION=4.0.3`, which is what OpenAPI metadata and the UI brand config expose. Local source runs fall back to `git describe` and then the repo-root `VERSION` file; local Docker builds can override explicitly:
444+
Published backend images are stamped from the Git release tag automatically. A `v4.0.4` tag becomes `APP_VERSION=4.0.4`, which is what OpenAPI metadata and the UI brand config expose. Local source runs fall back to `git describe` and then the repo-root `VERSION` file; local Docker builds can override explicitly:
445445

446446
```bash
447447
APP_VERSION=$(git describe --tags --abbrev=0 | sed 's/^v//') docker compose up -d --build backend
@@ -718,6 +718,9 @@ Economic calendar data is free-first: QuantDinger uses the no-key AkShare/Wallst
718718
| Doc | Notes |
719719
|-----|--------|
720720
| [Human Web API (OpenAPI)](docs/api/openapi.yaml) | flask-smorest spec — browse with [`docs/api/index.html`](docs/api/index.html) (HTTP server required) |
721+
| [Backend architecture](docs/ARCHITECTURE.md) | Runtime surfaces, ownership boundaries, and refactor rules |
722+
| [Extension guide](docs/EXTENSION_GUIDE.md) | How to add APIs, adapters, data sources, workers, and strategy features |
723+
| [Module boundaries](docs/MODULE_BOUNDARIES.md) | Deeper backend module ownership notes |
721724
| [API conventions](docs/API_CONVENTIONS.md) | Auth, envelopes, visibility tiers |
722725
| [Changelog](docs/CHANGELOG.md) | Releases & migrations |
723726
| [README (Chinese)](docs/README_CN.md) | Chinese overview |

‎VERSION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4.0.3
1+
4.0.4

‎backend_api_python/VERSION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4.0.3
1+
4.0.4

‎backend_api_python/app/config/__init__.py‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
"""
2-
配置模块
3-
统一导出所有配置
4-
"""
1+
"""Configuration package exports."""
52
from app.config.settings import Config
63
from app.config.api_keys import APIKeys
74
from app.config.database import RedisConfig, CacheConfig

‎backend_api_python/app/config/api_keys.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os
66

77
class MetaAPIKeys(type):
8-
"""API Keys 元类,用于支持类属性的动态获取"""
8+
"""Metaclass that supports dynamic class-level API key lookup."""
99

1010
@property
1111
def FINNHUB_API_KEY(cls):
@@ -226,17 +226,17 @@ def SERPAPI_KEYS(cls):
226226

227227

228228
class APIKeys(metaclass=MetaAPIKeys):
229-
"""API 密钥配置类"""
229+
"""API key configuration."""
230230

231231
@classmethod
232232
def get(cls, key_name: str, default: str = '') -> str:
233-
"""获取 API 密钥"""
233+
"""Return an API key by name."""
234234
if hasattr(cls, key_name):
235235
return getattr(cls, key_name)
236236
return default
237237

238238
@classmethod
239239
def is_configured(cls, key_name: str) -> bool:
240-
"""检查 API 密钥是否已配置"""
240+
"""Return whether an API key is configured."""
241241
value = cls.get(key_name)
242242
return bool(value and value.strip())

‎backend_api_python/app/config/database.py‎

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
"""
2-
数据库和缓存配置
3-
"""
1+
"""Database and cache configuration."""
42
import os
53

64
class MetaRedisConfig(type):
7-
"""Redis 配置"""
5+
"""Redis connection settings."""
86

97
@property
108
def HOST(cls):
@@ -36,18 +34,18 @@ def MAX_CONNECTIONS(cls):
3634

3735

3836
class RedisConfig(metaclass=MetaRedisConfig):
39-
"""Redis 缓存配置"""
37+
"""Redis cache configuration."""
4038

4139
@classmethod
4240
def get_url(cls) -> str:
43-
"""获取 Redis 连接 URL"""
41+
"""Return the Redis connection URL."""
4442
if cls.PASSWORD:
4543
return f"redis://:{cls.PASSWORD}@{cls.HOST}:{cls.PORT}/{cls.DB}"
4644
return f"redis://{cls.HOST}:{cls.PORT}/{cls.DB}"
4745

4846

4947
class MetaCacheConfig(type):
50-
"""缓存业务配置"""
48+
"""Business cache settings."""
5149

5250
@property
5351
def ENABLED(cls):
@@ -60,14 +58,14 @@ def DEFAULT_EXPIRE(cls):
6058
@property
6159
def KLINE_CACHE_TTL(cls):
6260
return {
63-
'1m': 5, # 1分钟K线缓存5秒
64-
'3m': 30, # 3分钟K线缓存30秒
65-
'5m': 60, # 5分钟K线缓存1分钟
66-
'15m': 300, # 15分钟K线缓存5分钟
67-
'30m': 300, # 30分钟K线缓存5分钟
68-
'1H': 300, # 1小时K线缓存5分钟
69-
'4H': 300, # 4小时K线缓存5分钟
70-
'1D': 300, # 日K线缓存5分钟
61+
'1m': 5, # Cache 1m K-lines for 5 seconds
62+
'3m': 30, # Cache 3m K-lines for 30 seconds
63+
'5m': 60, # Cache 5m K-lines for 1 minute
64+
'15m': 300, # Cache 15m K-lines for 5 minutes
65+
'30m': 300, # Cache 30m K-lines for 5 minutes
66+
'1H': 300, # Cache 1H K-lines for 5 minutes
67+
'4H': 300, # Cache 4H K-lines for 5 minutes
68+
'1D': 300, # Cache 1D K-lines for 5 minutes
7169
'1h': 300,
7270
'4h': 300,
7371
'1d': 300,
@@ -83,5 +81,5 @@ def PRICE_CACHE_TTL(cls):
8381

8482

8583
class CacheConfig(metaclass=MetaCacheConfig):
86-
"""缓存配置"""
84+
"""Cache configuration."""
8785
pass

‎backend_api_python/app/config/settings.py‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
"""
2-
应用主配置
3-
"""
1+
"""Application settings."""
42
import os
53

64
class MetaConfig(type):
@@ -83,9 +81,9 @@ def ENABLE_REQUEST_LOG(cls):
8381
return os.getenv('ENABLE_REQUEST_LOG', 'True').lower() == 'true'
8482

8583
class Config(metaclass=MetaConfig):
86-
"""应用配置类"""
84+
"""Application configuration."""
8785

8886
@classmethod
8987
def get_log_path(cls) -> str:
90-
"""获取日志文件完整路径"""
88+
"""Return the full log file path."""
9189
return os.path.join(cls.LOG_DIR, cls.LOG_FILE)

‎backend_api_python/app/data_sources/base.py‎

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
2-
数据源基类
3-
定义统一的数据源接口
2+
Base market data source interfaces.
3+
4+
All market data adapters should normalize K-line rows to the shape defined here.
45
"""
56
from abc import ABC, abstractmethod
67
from typing import Dict, List, Any, Optional
@@ -25,7 +26,7 @@
2526

2627

2728
class BaseDataSource(ABC):
28-
"""数据源基类"""
29+
"""Base class for market data sources."""
2930

3031
name: str = "base"
3132

@@ -39,17 +40,17 @@ def get_kline(
3940
after_time: Optional[int] = None,
4041
) -> List[Dict[str, Any]]:
4142
"""
42-
获取K线数据
43+
Fetch K-line data.
4344
4445
Args:
45-
symbol: 交易对/股票代码
46-
timeframe: 时间周期 (1m, 5m, 15m, 30m, 1H, 4H, 1D, 1W)
47-
limit: 数据条数
48-
before_time: 获取此时间之前的数据(Unix时间戳,秒)
49-
after_time: 可选,仅保留 time >= after_time 的 K 线(Unix 秒),用于回测窗口左边界
46+
symbol: Trading pair or ticker.
47+
timeframe: Candle interval (1m, 5m, 15m, 30m, 1H, 4H, 1D, 1W).
48+
limit: Number of rows to fetch.
49+
before_time: Fetch rows before this Unix timestamp, in seconds.
50+
after_time: Optional left boundary. Keep only rows with time >= after_time.
5051
5152
Returns:
52-
K线数据列表,格式:
53+
K-line rows in this normalized shape:
5354
[{"time": int, "open": float, "high": float, "low": float, "close": float, "volume": float}, ...]
5455
"""
5556
pass
@@ -72,7 +73,7 @@ def format_kline(
7273
close: float,
7374
volume: float
7475
) -> Dict[str, Any]:
75-
"""格式化单条K线数据"""
76+
"""Normalize one K-line row."""
7677
return {
7778
'time': timestamp,
7879
'open': round(float(open_price), 4),
@@ -89,15 +90,15 @@ def calculate_time_range(
8990
buffer_ratio: float = 1.2
9091
) -> int:
9192
"""
92-
计算获取指定数量K线所需的时间范围(秒)
93+
Calculate the time range required to fetch the requested candle count.
9394
9495
Args:
95-
timeframe: 时间周期
96-
limit: K线数量
97-
buffer_ratio: 缓冲系数
96+
timeframe: Candle interval.
97+
limit: Number of candles.
98+
buffer_ratio: Extra range multiplier.
9899
99100
Returns:
100-
时间范围(秒)
101+
Time range in seconds.
101102
"""
102103
seconds_per_candle = TIMEFRAME_SECONDS.get(timeframe, 86400)
103104
return int(seconds_per_candle * limit * buffer_ratio)
@@ -111,17 +112,18 @@ def filter_and_limit(
111112
truncate: bool = True,
112113
) -> List[Dict[str, Any]]:
113114
"""
114-
过滤和限制K线数据
115+
Filter and limit K-line rows.
115116
116117
Args:
117-
klines: K线数据列表
118-
limit: 最大数量
119-
before_time: 过滤此时间之后的数据
120-
after_time: 若设置,仅保留 time >= after_time
121-
truncate: 为 False 时不在末尾按 limit 截断(回测需整段 [after_time, before_time) 时避免误丢左端)
118+
klines: K-line rows.
119+
limit: Maximum number of rows.
120+
before_time: Keep rows before this timestamp.
121+
after_time: Keep rows with time >= after_time when set.
122+
truncate: When False, do not trim the tail by limit. Backtests need the
123+
full [after_time, before_time) window and must not lose the left edge.
122124
123125
Returns:
124-
处理后的K线数据
126+
Filtered K-line rows.
125127
"""
126128
klines.sort(key=lambda x: x['time'])
127129

@@ -141,12 +143,14 @@ def log_result(
141143
klines: List[Dict[str, Any]],
142144
timeframe: str
143145
):
144-
"""记录获取结果日志。
146+
"""Log fetch result quality.
145147
146-
延迟判断:
147-
- K 线 time 为 Unix 秒(UTC),与 datetime.now(UTC) 比较,避免本地时区误差。
148-
- 日线/周线:最后一根通常是「上一交易日收盘」,周末/节假日可达 3~4 天,
149-
原先用 2×86400s(48h)会在周一早盘误报;改为日线最多容忍约 5 个自然日,周线更宽。
148+
Delay checks:
149+
- K-line time is a UTC Unix timestamp. Compare with datetime.now(UTC) to
150+
avoid local timezone drift.
151+
- Daily and weekly bars often represent the previous market close. Weekends
152+
and holidays can create a 3-4 day gap, so daily bars allow about 5
153+
calendar days and weekly bars allow a wider threshold.
150154
"""
151155
if klines:
152156
latest_ts = int(klines[-1]["time"])

0 commit comments

Comments
 (0)