Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 4 additions & 2 deletions src/risk/position_sizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import numpy as np

from src.utils.ashare_constants import LOT_SIZE, MAX_SINGLE_POSITION

logger = logging.getLogger(__name__)


Expand All @@ -39,8 +41,8 @@ class SizingResult:
class PositionSizingConfig:
"""Configuration for position sizing."""

max_single_weight: float = 0.30
min_lot_size: int = 100
max_single_weight: float = MAX_SINGLE_POSITION
min_lot_size: int = LOT_SIZE
kelly_fraction: float = 0.25
target_volatility: float = 0.15
max_leverage: float = 1.0
Expand Down
25 changes: 16 additions & 9 deletions src/strategy/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@

import pandas as pd

from src.utils.ashare_constants import (
COMMISSION_RATE,
LOT_SIZE,
PRICE_LIMITS,
STAMP_DUTY_RATE,
)
from src.utils.config import load_config
from src.utils.logger import get_logger

Expand All @@ -27,12 +33,9 @@
BOARD_CHINEXT: str = "chinext"
BOARD_STAR: str = "star"

# Price limit percentages by board type
PRICE_LIMITS: dict[str, float] = {
BOARD_MAIN: 0.10,
BOARD_CHINEXT: 0.20,
BOARD_STAR: 0.20,
}
# Price limits by board live in the canonical constants module and are imported
# above (re-exported here for back-compat; backtest/engine imports PRICE_LIMITS
# from this module).


class BaseStrategy(ABC):
Expand All @@ -50,10 +53,14 @@ class BaseStrategy(ABC):
def __init__(self, config_path: str = "strategy") -> None:
self.config: dict[str, Any] = load_config(config_path)
self.common_config: dict[str, Any] = self.config.get("common", {})
self.min_lot_size: int = self.common_config.get("min_lot_size", 100)
self.commission_rate: float = self.common_config.get("commission_rate", 0.0003)
self.min_lot_size: int = self.common_config.get("min_lot_size", LOT_SIZE)
self.commission_rate: float = self.common_config.get(
"commission_rate", COMMISSION_RATE
)
self.min_commission: float = self.common_config.get("min_commission", 5.0)
self.stamp_tax_rate: float = self.common_config.get("stamp_tax_rate", 0.001)
self.stamp_tax_rate: float = self.common_config.get(
"stamp_tax_rate", STAMP_DUTY_RATE
)
self.stop_loss: float = self.common_config.get("stop_loss", 0.08)
self.take_profit: float = self.common_config.get("take_profit", 0.15)
self.position_size: float = self.common_config.get("position_size", 0.3)
Expand Down
17 changes: 11 additions & 6 deletions src/trading/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@
from datetime import date, datetime
from typing import Any

from src.utils.ashare_constants import (
CHINEXT_PREFIXES,
COMMISSION_RATE,
LOT_SIZE,
STAMP_DUTY_RATE,
STAR_PREFIXES,
)
from src.utils.config import load_config

logger = logging.getLogger(__name__)

# A-share exchange prefixes
CHINEXT_PREFIXES = ("300", "301") # 创业板
STAR_PREFIXES = ("688", "689") # 科创板
# A-share exchange prefixes (CHINEXT/STAR from the canonical constants module)
BSE_PREFIXES = ("8",) # 北交所
MAIN_BOARD_SH = ("600", "601", "603", "605") # 沪市主板
MAIN_BOARD_SZ = ("000", "001", "002", "003") # 深市主板
Expand Down Expand Up @@ -71,9 +76,9 @@ def __init__(self, config: dict[str, Any] | None = None) -> None:
)
self._max_chase_pct = self._config.get("max_intraday_chase_pct", 5.0)
self._min_daily_turnover_wan = self._config.get("min_daily_turnover_wan", 5000)
self._lot_size = self._config.get("lot_size", 100)
self._commission_rate = self._config.get("commission_rate", 0.0003)
self._stamp_duty_rate = self._config.get("stamp_duty_rate", 0.001)
self._lot_size = self._config.get("lot_size", LOT_SIZE)
self._commission_rate = self._config.get("commission_rate", COMMISSION_RATE)
self._stamp_duty_rate = self._config.get("stamp_duty_rate", STAMP_DUTY_RATE)
self._min_commission = self._config.get("min_commission", 5.0)
logger.info("TradingConstraintsEngine initialized")

Expand Down
39 changes: 39 additions & 0 deletions src/utils/ashare_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Canonical A-share market constants — single source of truth.

The lot size, commission / stamp-duty rates, max-position cap, and board daily
price limits were previously re-declared as literal defaults in several modules
(``trading/constraints``, ``strategy/base``, ``backtest/engine``,
``risk/position_sizer``, ``agent_loop/ashare_constraints``). That duplication is
a drift hazard. Import these instead of re-hardcoding the values.

Scope (#47): this consolidates the shared *constants*. Each module's board
*detection* logic and rule application still live locally — unifying those is a
deeper, behavior-sensitive follow-up tracked on the same issue.
"""

from __future__ import annotations

# Minimum tradable unit: 1 lot = 100 shares (1 手 = 100 股).
LOT_SIZE: int = 100

# Transaction costs.
COMMISSION_RATE: float = 0.0003 # brokerage commission (both sides)
STAMP_DUTY_RATE: float = 0.001 # stamp duty (sell side only)

# Default max weight for a single position.
MAX_SINGLE_POSITION: float = 0.30

# Daily price-limit magnitude by board, as a decimal fraction.
# main board ±10%
# ChiNext / STAR ±20%
# BSE ±30%
PRICE_LIMITS: dict[str, float] = {
"main": 0.10,
"chinext": 0.20,
"star": 0.20,
"bse": 0.30,
}

# Board code prefixes (6-digit A-share codes).
CHINEXT_PREFIXES: tuple[str, ...] = ("300", "301")
STAR_PREFIXES: tuple[str, ...] = ("688", "689")
Loading