quantification 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. quantification/__init__.py +3 -2
  2. quantification/api/__init__.py +3 -0
  3. quantification/api/akshare/__init__.py +1 -0
  4. quantification/api/akshare/akshare.py +17 -0
  5. quantification/api/akshare/delegate/__init__.py +6 -0
  6. quantification/api/akshare/delegate/macro_china_fdi.py +46 -0
  7. quantification/api/akshare/delegate/macro_china_lpr.py +43 -0
  8. quantification/api/akshare/delegate/macro_china_qyspjg.py +51 -0
  9. quantification/api/akshare/delegate/macro_china_shrzgm.py +47 -0
  10. quantification/api/akshare/delegate/macro_cnbs.py +47 -0
  11. quantification/api/akshare/delegate/stock_zh_a_hist.py +77 -0
  12. quantification/api/akshare/setting.py +5 -0
  13. quantification/api/api.py +11 -0
  14. quantification/api/api.pyi +21 -0
  15. quantification/api/tushare/__init__.py +1 -0
  16. quantification/api/tushare/delegate/__init__.py +7 -0
  17. quantification/api/tushare/delegate/balancesheet.py +66 -0
  18. quantification/api/tushare/delegate/cashflow.py +29 -0
  19. quantification/api/tushare/delegate/common.py +64 -0
  20. quantification/api/tushare/delegate/daily_basic.py +81 -0
  21. quantification/api/tushare/delegate/fina_indicator.py +20 -0
  22. quantification/api/tushare/delegate/income.py +34 -0
  23. quantification/api/tushare/delegate/index_daily.py +61 -0
  24. quantification/api/tushare/delegate/pro_bar.py +80 -0
  25. quantification/api/tushare/setting.py +5 -0
  26. quantification/api/tushare/tushare.py +17 -0
  27. quantification/core/__init__.py +9 -0
  28. quantification/core/asset/__init__.py +6 -0
  29. quantification/core/asset/base_asset.py +96 -0
  30. quantification/core/asset/base_broker.py +42 -0
  31. quantification/core/asset/broker.py +108 -0
  32. quantification/core/asset/cash.py +75 -0
  33. quantification/core/asset/stock.py +268 -0
  34. quantification/core/cache.py +93 -0
  35. quantification/core/configure.py +15 -0
  36. quantification/core/data/__init__.py +5 -0
  37. quantification/core/data/base_api.py +109 -0
  38. quantification/core/data/base_delegate.py +73 -0
  39. quantification/core/data/field.py +213 -0
  40. quantification/core/data/panel.py +42 -0
  41. quantification/core/env.py +25 -0
  42. quantification/core/logger.py +94 -0
  43. quantification/core/strategy/__init__.py +3 -0
  44. quantification/core/strategy/base_strategy.py +66 -0
  45. quantification/core/strategy/base_trigger.py +69 -0
  46. quantification/core/strategy/base_use.py +69 -0
  47. quantification/core/trader/__init__.py +7 -0
  48. quantification/core/trader/base_order.py +45 -0
  49. quantification/core/trader/base_stage.py +16 -0
  50. quantification/core/trader/base_trader.py +173 -0
  51. quantification/core/trader/collector.py +47 -0
  52. quantification/core/trader/order.py +23 -0
  53. quantification/core/trader/portfolio.py +72 -0
  54. quantification/core/trader/query.py +29 -0
  55. quantification/core/trader/report.py +76 -0
  56. quantification/core/util.py +181 -0
  57. quantification/default/__init__.py +5 -0
  58. quantification/default/stage/__init__.py +1 -0
  59. quantification/default/stage/cn_stock.py +23 -0
  60. quantification/default/strategy/__init__.py +1 -0
  61. quantification/default/strategy/simple/__init__.py +1 -0
  62. quantification/default/strategy/simple/strategy.py +8 -0
  63. quantification/default/trader/__init__.py +2 -0
  64. quantification/default/trader/a_factor/__init__.py +1 -0
  65. quantification/default/trader/a_factor/trader.py +27 -0
  66. quantification/default/trader/simple/__init__.py +1 -0
  67. quantification/default/trader/simple/trader.py +8 -0
  68. quantification/default/trigger/__init__.py +1 -0
  69. quantification/default/trigger/trigger.py +63 -0
  70. quantification/default/use/__init__.py +1 -0
  71. quantification/default/use/factors/__init__.py +2 -0
  72. quantification/default/use/factors/factor.py +205 -0
  73. quantification/default/use/factors/use.py +38 -0
  74. quantification-0.1.1.dist-info/METADATA +19 -0
  75. quantification-0.1.1.dist-info/RECORD +76 -0
  76. {quantification-0.1.0.dist-info → quantification-0.1.1.dist-info}/WHEEL +1 -1
  77. quantification-0.1.0.dist-info/METADATA +0 -13
  78. quantification-0.1.0.dist-info/RECORD +0 -4
@@ -0,0 +1,173 @@
1
+ from tqdm import tqdm
2
+ from typing import TypeVar, Generic, TYPE_CHECKING
3
+ from datetime import date, timedelta, datetime
4
+
5
+ from .query import Query
6
+ from .report import AssetData, OrderResultData, PeriodData, PointData, BenchmarkData, ReportData
7
+ from .portfolio import Portfolio
8
+ from .collector import Collector
9
+ from .base_order import Result, BaseOrder
10
+ from .base_stage import BaseStage
11
+ from ..asset.cash import RMB
12
+ from ..data.field import Field
13
+
14
+ from ..env import EnvGetter, Env
15
+ from ..logger import logger
16
+
17
+ if TYPE_CHECKING:
18
+ from ..data import BaseAPI
19
+ from ..asset import BaseBroker, BaseAsset
20
+ from ..strategy import BaseStrategy
21
+
22
+ StrategyType = TypeVar("StrategyType", bound="BaseStrategy")
23
+
24
+
25
+ class BaseTrader(Generic[StrategyType]):
26
+ def __init__(
27
+ self,
28
+ api: "BaseAPI",
29
+ init_portfolio: Portfolio,
30
+ start_date: date,
31
+ end_date: date,
32
+ padding: int,
33
+ stage: type[BaseStage],
34
+ strategy: StrategyType,
35
+ brokers: list[type["BaseBroker"]]
36
+ ):
37
+ self.api = api
38
+ self.start_date = start_date
39
+ self.end_date = end_date
40
+ self.stage = stage
41
+ self.strategy = strategy
42
+ self.portfolio = init_portfolio
43
+ self.brokers = [broker(api, start_date - timedelta(days=padding), end_date, stage) for broker in brokers]
44
+ self.query = Query(api, start_date - timedelta(days=padding), end_date)
45
+
46
+ self.collector = Collector()
47
+ self.env: Env | None = None
48
+ EnvGetter.getter = lambda: self.env
49
+
50
+ def match_broker(self, asset: "BaseAsset") -> "BaseBroker|None":
51
+ for candidate_broker in self.brokers:
52
+ if candidate_broker.matchable(asset):
53
+ return candidate_broker
54
+
55
+ return None
56
+
57
+ @property
58
+ def timeline(self):
59
+ current_date = self.start_date
60
+ while current_date <= self.end_date:
61
+ for current_stage in self.stage:
62
+ self.env = Env(date=current_date, time=current_stage.time)
63
+ self.collector.commence(current_date, current_stage, self.portfolio)
64
+ yield current_date, current_stage
65
+ current_date += timedelta(days=1)
66
+
67
+ def run(self):
68
+ for day, stage in self.timeline:
69
+ logger.trace(f"==========日期:{day}===阶段:{stage}==========")
70
+ params = {
71
+ "day": day,
72
+ "stage": stage,
73
+ "portfolio": self.portfolio,
74
+ "context": self.strategy.context,
75
+ "query": self.query,
76
+ "trader": self,
77
+ "strategy": self.strategy
78
+ }
79
+
80
+ hooks = self.strategy.triggered(**params)
81
+ logger.trace(f"触发的全部hooks:{hooks}")
82
+
83
+ for hook in hooks:
84
+ logger.trace(f"开始运行{hook}")
85
+ gen = hook(**params)
86
+ order: BaseOrder | None = None
87
+ result: Result | None = None
88
+
89
+ while True:
90
+ try:
91
+ order = gen.send(result) if order else next(gen)
92
+ logger.trace(f"{hook}发出Order:{order}, 开始匹配Broker")
93
+
94
+ assert isinstance(order, BaseOrder), f"只能yield Order, 实际为{type(order)}"
95
+
96
+ broker = self.match_broker(order.asset)
97
+
98
+ if not broker:
99
+ logger.warning(f"{order}没有对应broker, 忽略该order")
100
+ result = None
101
+ continue
102
+
103
+ logger.trace(f"{broker}开始处理:{order}")
104
+ result = broker.execute_order(order)
105
+ logger.trace(f"{broker}处理完成:{result}")
106
+ self.handle_result(result)
107
+ except StopIteration:
108
+ logger.trace(f"运行结束{hook}")
109
+ break
110
+
111
+ def handle_result(self, result: Result):
112
+ self.portfolio += result.brought
113
+ self.portfolio -= result.sold
114
+ self.collector.collect(result, self.portfolio)
115
+ logger.trace(f"资产增加{result.brought}, 减少{result.sold}")
116
+
117
+ def liquidate(self, asset: "BaseAsset", day: date, stage: "BaseStage") -> int:
118
+ if isinstance(asset, RMB):
119
+ return asset.amount
120
+
121
+ if (broker := self.match_broker(asset)) is None:
122
+ return -1
123
+
124
+ return broker.liquidate_asset(asset, day, stage)
125
+
126
+ def report(self, title: str, description: str, benchmark: str) -> ReportData:
127
+ periods_data = []
128
+
129
+ for shard in tqdm(self.collector.shards, "生成报告"):
130
+ portfolios_data: list[AssetData] = []
131
+ total_liquidating_value = 0
132
+ for asset in shard.portfolio:
133
+ liquidating_value = self.liquidate(asset, shard.day, shard.stage)
134
+ total_liquidating_value += liquidating_value
135
+ portfolios_data.append(AssetData.from_asset(asset, liquidating_value=liquidating_value))
136
+
137
+ datetime_str = datetime.combine(shard.day, shard.stage.time).isoformat()
138
+ periods_data.append(PeriodData(
139
+ datetime=datetime_str,
140
+ liquidating_value=total_liquidating_value,
141
+ logs=logger.records.get(datetime_str, []),
142
+ portfolios=portfolios_data,
143
+ transactions=[OrderResultData.from_result(result) for result in shard.results]
144
+ ))
145
+
146
+ benchmark_points = [
147
+ PointData(
148
+ datetime=index.to_pydatetime().isoformat(),
149
+ value=row[Field.IN_收盘点位]
150
+ ) for index, row in self.api.query(
151
+ start_date=self.start_date,
152
+ end_date=self.end_date,
153
+ fields=[Field.IN_收盘点位],
154
+ index=benchmark,
155
+ ).iterrows()
156
+ ]
157
+
158
+ return ReportData(
159
+ title=title,
160
+ description=description,
161
+ start_date=self.start_date.isoformat(),
162
+ end_date=self.end_date.isoformat(),
163
+ init_value=periods_data[0].liquidating_value,
164
+ periods=periods_data,
165
+ benchmark=BenchmarkData(
166
+ name=benchmark,
167
+ init_value=benchmark_points[0].value,
168
+ points=benchmark_points
169
+ )
170
+ )
171
+
172
+
173
+ __all__ = ["BaseTrader"]
@@ -0,0 +1,47 @@
1
+ from datetime import date
2
+
3
+ from .portfolio import Portfolio
4
+ from .base_order import Result
5
+ from .base_stage import BaseStage
6
+
7
+
8
+ class Shard:
9
+ def __init__(
10
+ self,
11
+ day: date,
12
+ stage: BaseStage,
13
+ portfolio: Portfolio,
14
+ ):
15
+ self.day = day
16
+ self.stage = stage
17
+ self.portfolio = portfolio
18
+ self.results: list[Result] = []
19
+
20
+ def collect(self, result: Result):
21
+ self.results.append(result)
22
+
23
+ def __repr__(self):
24
+ return (
25
+ f"================{self.day} {self.stage.time}================\n"
26
+ f"Portfolio: {self.portfolio}\n"
27
+ f"Results: {self.results}\n"
28
+ )
29
+
30
+ __str__ = __repr__
31
+
32
+
33
+ class Collector:
34
+ def __init__(self):
35
+ self.shards: list[Shard] = []
36
+
37
+ def commence(
38
+ self,
39
+ day: date,
40
+ stage: BaseStage,
41
+ portfolio: Portfolio,
42
+ ):
43
+ self.shards.append(Shard(day, stage, portfolio.copy))
44
+
45
+ def collect(self, result: Result, portfolio: Portfolio):
46
+ self.shards[-1].collect(result)
47
+ self.shards[-1].portfolio = portfolio.copy
@@ -0,0 +1,23 @@
1
+ from typing import TYPE_CHECKING, Literal
2
+
3
+ from .base_order import BaseOrder
4
+
5
+ if TYPE_CHECKING:
6
+ from ..asset import Stock
7
+
8
+
9
+ class StockOrder(BaseOrder["Stock"]):
10
+ def __init__(self, asset: "Stock", category: Literal["buy"] | Literal["sell"]):
11
+ super().__init__(asset, category)
12
+
13
+ @property
14
+ def extra(self):
15
+ return {}
16
+
17
+ def __repr__(self):
18
+ return f"<股票交易指令 {self.category} {self.asset}>"
19
+
20
+ __str__ = __repr__
21
+
22
+
23
+ __all__ = ["StockOrder"]
@@ -0,0 +1,72 @@
1
+ from typing import TypeVar
2
+
3
+ from ..asset.base_asset import BaseAsset
4
+
5
+ AssetType = TypeVar('AssetType', bound=BaseAsset)
6
+
7
+
8
+ class Portfolio:
9
+ def __init__(self, *args: BaseAsset):
10
+ self.assets: list[BaseAsset] = list(args)
11
+
12
+ def add(self, asset: BaseAsset):
13
+ for index in range(len(self.assets)):
14
+ if self.assets[index] != asset:
15
+ continue
16
+
17
+ self.assets[index] += asset
18
+ break
19
+ else:
20
+ self.assets.append(asset)
21
+
22
+ def sub(self, asset: BaseAsset):
23
+ for index in range(len(self.assets)):
24
+ if self.assets[index] != asset:
25
+ continue
26
+
27
+ self.assets[index] -= asset
28
+ break
29
+ else:
30
+ raise IndexError(f"{self}没有资产{asset}")
31
+
32
+ self.assets = [i for i in self.assets if not (i.is_empty and i.is_closeable)]
33
+
34
+ def __add__(self, other: BaseAsset | list[BaseAsset]):
35
+ if isinstance(other, BaseAsset):
36
+ self.add(other)
37
+
38
+ if isinstance(other, list):
39
+ for asset in other:
40
+ assert isinstance(asset, BaseAsset), f"只允许添加资产, 实际为{type(asset)}"
41
+ self.add(asset)
42
+
43
+ return self
44
+
45
+ def __sub__(self, other: BaseAsset | list[BaseAsset]):
46
+ if isinstance(other, BaseAsset):
47
+ self.sub(other)
48
+
49
+ if isinstance(other, list):
50
+ for asset in other:
51
+ assert isinstance(asset, BaseAsset), f"只允许添加资产, 实际为{type(asset)}"
52
+ self.sub(asset)
53
+
54
+ return self
55
+
56
+ def __getitem__(self, item: type[AssetType]) -> list[AssetType]:
57
+ return [i for i in self.copy.assets if isinstance(i, item)]
58
+
59
+ def __iter__(self):
60
+ return iter(self.copy.assets)
61
+
62
+ @property
63
+ def copy(self) -> "Portfolio":
64
+ return Portfolio(*[asset.copy for asset in self.assets])
65
+
66
+ def __repr__(self):
67
+ return f"资产: {self.assets}"
68
+
69
+ __str__ = __repr__
70
+
71
+
72
+ __all__ = ["Portfolio"]
@@ -0,0 +1,29 @@
1
+ from typing import TYPE_CHECKING
2
+ from datetime import date, datetime
3
+
4
+ from ..env import EnvGetter
5
+
6
+ if TYPE_CHECKING:
7
+ from ..data import BaseAPI
8
+
9
+
10
+ class Query(EnvGetter):
11
+ def __init__(self, api: "BaseAPI", start_date: date, end_date: date):
12
+ super().__init__()
13
+
14
+ self.api = api
15
+ self.start_date = start_date
16
+ self.end_date = end_date
17
+
18
+ def __call__(self, **kwargs):
19
+ assert self.env is not None, "无法获取env"
20
+
21
+ params = {
22
+ "start_date": self.start_date,
23
+ "end_date": self.end_date,
24
+ }
25
+ params.update(kwargs)
26
+ return self.api.query(**params).on(datetime.combine(self.env.date, self.env.time))
27
+
28
+
29
+ __all__ = ['Query']
@@ -0,0 +1,76 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from ..logger import Record
6
+
7
+ if TYPE_CHECKING:
8
+ from .base_order import Result
9
+ from ..asset import BaseAsset
10
+
11
+
12
+ class AssetData(BaseModel):
13
+ type: str
14
+ name: str
15
+ amount: float
16
+ extra: dict
17
+
18
+ @classmethod
19
+ def from_asset(cls, asset: "BaseAsset", **kwargs) -> "AssetData":
20
+ return cls(
21
+ type=asset.type(),
22
+ name=asset.name(),
23
+ amount=asset.amount,
24
+ extra={**asset.extra, **kwargs}
25
+ )
26
+
27
+
28
+ class OrderResultData(BaseModel):
29
+ order_type: str
30
+ order_category: str
31
+ order_asset: AssetData
32
+ order_extra: dict
33
+ result_brought: list[AssetData]
34
+ result_sold: list[AssetData]
35
+
36
+ @classmethod
37
+ def from_result(cls, result: "Result", **kwargs) -> "OrderResultData":
38
+ return cls(
39
+ order_type=result.order.type(),
40
+ order_asset=AssetData.from_asset(result.order.asset),
41
+ order_category=result.order.category,
42
+ order_extra={**result.order.extra, **kwargs},
43
+ result_brought=[AssetData.from_asset(i) for i in result.brought],
44
+ result_sold=[AssetData.from_asset(i) for i in result.sold],
45
+ )
46
+
47
+
48
+ class PeriodData(BaseModel):
49
+ datetime: str
50
+ liquidating_value: float
51
+
52
+ logs: list[Record]
53
+ portfolios: list[AssetData]
54
+ transactions: list[OrderResultData]
55
+
56
+
57
+ class PointData(BaseModel):
58
+ datetime: str
59
+ value: float
60
+
61
+
62
+ class BenchmarkData(BaseModel):
63
+ name: str
64
+ init_value: float
65
+ points: list[PointData]
66
+
67
+
68
+ class ReportData(BaseModel):
69
+ title: str
70
+ description: str
71
+ start_date: str
72
+ end_date: str
73
+ init_value: float
74
+
75
+ benchmark: BenchmarkData
76
+ periods: list[PeriodData]
@@ -0,0 +1,181 @@
1
+ import types
2
+ import inspect
3
+ import functools
4
+ from typing import Callable, Any
5
+
6
+ from pydantic import ValidationError, create_model, ConfigDict
7
+ from pydantic_core import PydanticUndefined
8
+
9
+
10
+ def to_str(x: Callable[..., Any]) -> str:
11
+ """Convert a callable object to a descriptive string representation.
12
+
13
+ Args:
14
+ x: Any callable object (function, lambda, partial, class, method, etc.)
15
+
16
+ Returns:
17
+ A string representation of the callable.
18
+ """
19
+ # 1. Handle functools.partial objects
20
+ if isinstance(x, functools.partial):
21
+ func_str = to_str(x.func)
22
+ args = [repr(a) for a in x.args]
23
+ keywords = [f"{k}={repr(v)}" for k, v in x.keywords.items()]
24
+ all_args = ", ".join(args + keywords)
25
+ return f"functools.partial({func_str}, {all_args})"
26
+
27
+ # 2. Handle classes (since classes are callable)
28
+ if inspect.isclass(x):
29
+ return f"{x.__module__}.{x.__qualname__}"
30
+
31
+ # 3. Handle bound methods (instance methods and class methods)
32
+ if isinstance(x, (types.MethodType, types.BuiltinMethodType)):
33
+ method_name = x.__name__
34
+ owner = x.__self__
35
+
36
+ if inspect.isclass(owner):
37
+ owner_str = owner.__qualname__
38
+ return f"<bound method {owner_str}.{method_name}>"
39
+
40
+ class_name = owner.__class__.__qualname__
41
+ return f"<bound method {class_name}.{method_name} of {repr(owner)}>"
42
+
43
+ if isinstance(x, (types.FunctionType, types.BuiltinFunctionType)):
44
+ module = x.__module__
45
+ qualname = x.__qualname__
46
+
47
+ if qualname == '<lambda>':
48
+ try:
49
+ source = inspect.getsource(x).strip()
50
+ if '\n' in source:
51
+ return f"<lambda at {module}:{x.__code__.co_firstlineno}>"
52
+ return source
53
+ except (OSError, TypeError):
54
+ return "<lambda>"
55
+
56
+ return f"{module}.{qualname}"
57
+
58
+ # Fallback: Use standard string representation
59
+ return str(x)
60
+
61
+
62
+ def get_function_location(func) -> str:
63
+ """获取函数的定义位置(文件路径和行号)"""
64
+ try:
65
+ # 尝试获取函数的源文件和行号
66
+ source_file = inspect.getsourcefile(func)
67
+ lines, start_line = inspect.getsourcelines(func)
68
+ end_line = start_line + len(lines) - 1
69
+
70
+ # 如果是lambda函数,使用特殊格式
71
+ if func.__name__ == '<lambda>':
72
+ return f"lambda at {source_file}:{start_line}"
73
+
74
+ return f"{source_file}:{start_line}-{end_line}"
75
+ except Exception:
76
+ # 如果无法获取位置信息,返回默认值
77
+ return "<unknown location>"
78
+
79
+
80
+ def format_arg_type(value) -> str:
81
+ """格式化参数类型信息,特别处理类对象"""
82
+ if inspect.isclass(value):
83
+ # 对于类对象,显示类名而不是元类
84
+ return f"<class '{value.__module__}.{value.__qualname__}'>"
85
+ return str(type(value))
86
+
87
+
88
+ def format_annotations(params) -> str:
89
+ """格式化参数注解信息"""
90
+ lines = []
91
+ for name, param in params.items():
92
+ # 跳过可变关键字参数
93
+ if param.kind == inspect.Parameter.VAR_KEYWORD:
94
+ continue
95
+
96
+ # 获取类型注解
97
+ ann = param.annotation
98
+ ann_str = ann.__name__ if ann is not param.empty else "Any"
99
+
100
+ # 添加默认值信息
101
+ default = ""
102
+ if param.default is not param.empty:
103
+ default = f" (默认值: {repr(param.default)})"
104
+
105
+ # 添加参数类型标记
106
+ kind = ""
107
+ if param.kind == inspect.Parameter.KEYWORD_ONLY:
108
+ kind = " [keyword-only]"
109
+ elif param.kind == inspect.Parameter.POSITIONAL_ONLY:
110
+ kind = " [positional-only]"
111
+
112
+ lines.append(f" - {name}: {ann_str}{default}{kind}")
113
+ return "\n".join(lines)
114
+
115
+
116
+ def inject(func, **kwargs) -> Any:
117
+ sig = inspect.signature(func)
118
+ params = sig.parameters
119
+
120
+ model_fields = {}
121
+ for name, param in params.items():
122
+ if param.kind == inspect.Parameter.VAR_KEYWORD:
123
+ continue
124
+
125
+ annotation = param.annotation
126
+ if annotation is param.empty:
127
+ annotation = Any
128
+
129
+ default = PydanticUndefined
130
+ if param.default is not param.empty:
131
+ default = param.default
132
+
133
+ model_fields[name] = (annotation, default)
134
+
135
+ func_name = to_str(func)
136
+
137
+ model = create_model(
138
+ f"{func_name}_Params",
139
+ __config__=ConfigDict(
140
+ extra="allow",
141
+ arbitrary_types_allowed=True,
142
+ coerce_numbers_to_str=True
143
+ ),
144
+ **model_fields
145
+ )
146
+
147
+ try:
148
+ validated = model(**kwargs)
149
+ except ValidationError as e:
150
+ errors = []
151
+ for error in e.errors():
152
+ loc = ".".join(map(str, error["loc"]))
153
+ expected_type = " | ".join(error.get("ctx", {}).get("expected", ["未知类型"]))
154
+ actual_type = type(kwargs.get(loc, "<missing>")).__name__
155
+ msg = f"无法将类型 {actual_type} 转换为函数定义的类型 {expected_type}"
156
+ input_value = error.get("input", "<missing>")
157
+
158
+ errors.append(f" - {loc}: {msg} (输入值: {input_value})")
159
+
160
+ arg_types = "\n".join([
161
+ f" - {k}: {format_arg_type(v)}"
162
+ for k, v in kwargs.items()
163
+ ])
164
+
165
+ raise TypeError(
166
+ f"🚫 参数类型与可用参数不兼容,且强制转换失败,请检查 {func_name} 的定义:\n"
167
+ f"📋 定义的参数:\n{format_annotations(params)}\n"
168
+ f"📤 可用的全部参数(正确标准):\n{arg_types}\n"
169
+ f"❌ 参数类型不匹配且转换失败:\n{"\n".join(errors)}\n"
170
+ f"📍 函数定义位置: {get_function_location(func)}\n"
171
+ )
172
+
173
+ validated_data = validated.model_dump()
174
+
175
+ if any((name for name, param in params.items() if param.kind == inspect.Parameter.VAR_KEYWORD)):
176
+ return func(**validated_data)
177
+
178
+ return func(**{p.name: validated_data[p.name] for p in params.values()})
179
+
180
+
181
+ __all__ = ["inject"]
@@ -0,0 +1,5 @@
1
+ from .use import *
2
+ from .stage import *
3
+ from .trader import *
4
+ from .trigger import *
5
+ from .strategy import *
@@ -0,0 +1 @@
1
+ from .cn_stock import StockStageCN
@@ -0,0 +1,23 @@
1
+ from enum import auto
2
+ from datetime import time
3
+
4
+ from quantification import BaseStage
5
+
6
+
7
+ class StockStageCN(BaseStage):
8
+ 盘前 = auto()
9
+ 开盘 = auto()
10
+ 收盘 = auto()
11
+ 盘后 = auto()
12
+
13
+ @property
14
+ def time(self) -> time:
15
+ return {
16
+ StockStageCN.盘前: time(0, 0),
17
+ StockStageCN.开盘: time(9, 30),
18
+ StockStageCN.收盘: time(15, 0),
19
+ StockStageCN.盘后: time(23, 59),
20
+ }[self]
21
+
22
+
23
+ __all__ = ['StockStageCN']
@@ -0,0 +1 @@
1
+ from .simple import *
@@ -0,0 +1 @@
1
+ from .strategy import SimpleStrategy
@@ -0,0 +1,8 @@
1
+ from quantification import BaseStrategy
2
+
3
+
4
+ class SimpleStrategy(BaseStrategy):
5
+ ...
6
+
7
+
8
+ __all__ = ['SimpleStrategy']
@@ -0,0 +1,2 @@
1
+ from .simple import *
2
+ from .a_factor import *
@@ -0,0 +1 @@
1
+ from .trader import AFactorTrader
@@ -0,0 +1,27 @@
1
+ from quantification import (
2
+ RMB,
3
+ Stock,
4
+ Portfolio,
5
+ BaseTrader,
6
+ StockBrokerCN
7
+ )
8
+
9
+
10
+ class AFactorTrader(BaseTrader):
11
+ def __init__(
12
+ self,
13
+ api,
14
+ start_date,
15
+ end_date,
16
+ stage,
17
+ strategy,
18
+ stocks: list[type[Stock]],
19
+ init_cash: float = 100_000,
20
+ padding: int = 0
21
+ ):
22
+ super().__init__(api, Portfolio(RMB(init_cash)), start_date, end_date, padding, stage, strategy,
23
+ [StockBrokerCN])
24
+ self.stocks = stocks
25
+
26
+
27
+ __all__ = ["AFactorTrader"]
@@ -0,0 +1 @@
1
+ from .trader import SimpleTrader