Skip to content

Commit 245822c

Browse files
committed
Add agent onboarding documentation and enhance API for active instruments
- Introduced AGENTS.md to provide guidelines on project structure, architecture, coding style, testing, and commit practices for agent onboarding. - Added new APIs: `active_instrument`, `active_instruments`, `instrument_history`, and `instruments_history` to improve instrument retrieval and historical data access. - Updated existing API methods to ensure consistency and clarity in handling active instruments and their histories. - Enhanced integration tests to validate the new APIs and ensure proper functionality with active and inactive instruments.
1 parent b33b310 commit 245822c

File tree

5 files changed

+234
-7
lines changed

5 files changed

+234
-7
lines changed

AGENTS.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Repository Guidelines
2+
3+
## Project Structure and Module Organization
4+
- `rqalpha/` is the main package; core engine code is under `rqalpha/core/`, and built-in mods under `rqalpha/mod/`.
5+
- `rqalpha/examples/`, `strategy_test/`, and `test_strategy/` provide sample strategies/configs.
6+
- `tests/` is split into `tests/unittest/` and `tests/integration_tests/`; docs live in `docs/`.
7+
8+
## Architecture Overview (for Agent Onboarding)
9+
- The engine is event-driven: `rqalpha/core/executor.py` consumes events and publishes them through `rqalpha/core/events.py`.
10+
- Strategy lifecycle hooks live in `rqalpha/core/strategy.py` (`init`, `before_trading`, `handle_bar`, `handle_tick`, `open_auction`, `after_trading`) with pre/post phases for mod hooks.
11+
- `rqalpha/environment.py` is the runtime registry for core services (data proxy/source, broker, portfolio, mods).
12+
- Extension contracts are defined in `rqalpha/interface.py`; new data/execution integrations should implement these.
13+
14+
## Build, Test, and Development Commands
15+
- `pip install -e .` installs the package and `rqalpha` CLI.
16+
- `rqalpha run -f path/to/strategy.py -s YYYY-MM-DD -e YYYY-MM-DD` runs a backtest.
17+
- `make -C docs html` builds documentation locally.
18+
19+
## Coding Style and Naming Conventions
20+
- Follow PEP 8: 4-space indentation, `snake_case` functions/variables, `CapWords` classes, `UPPER_SNAKE_CASE` constants.
21+
- Keep APIs, errors, and type hints consistent with existing `rqalpha/` modules.
22+
23+
## Testing Guidelines
24+
- Use `pytest`; run `pytest tests` for the full suite.
25+
- Unit tests target individual modules under `tests/unittest/`.
26+
- Integration tests under `tests/integration_tests/` run full backtests via `rqalpha.run_func`.
27+
- Pattern to follow (see `tests/integration_tests/test_backtest_results/`): define strategy callbacks in-test, build a `config` dict, and use `run_and_assert_result` to compare outputs against `outs/*.txt`.
28+
- If not explicitly told otherwise, prefer strategy-style integration tests that exercise the full framework with real data. Agents may read bundle data or call `rqdatac` to pick concrete instruments and dates.
29+
30+
## Commit and Pull Request Guidelines
31+
- Recent commits use imperative, sentence-case summaries (e.g., "Refactor ...", "Fix ...", "Update ..."). Keep messages short and scoped.
32+
- PRs should describe the change, link related issues if any, and include test results or rationale when tests are not run.
33+
34+
## Agent-Specific Pointers
35+
- Entry points: `rqalpha/__main__.py`, `rqalpha/cmds/run.py`, and `rqalpha/main.py`.
36+
- For strategy behavior: `rqalpha/core/strategy.py` and `rqalpha/core/events.py`.
37+
- For data/instruments: `rqalpha/data/` and `rqalpha/interface.py`; for orders/portfolio: `rqalpha/portfolio/` and `rqalpha/mod/rqalpha_mod_sys_accounts/`.
38+
39+
## Documentation and Data Notes
40+
- Update `docs/` when public behavior changes.
41+
- Data access and strategy behavior are tightly coupled to RQAlpha/RQData; avoid changing data contracts without migration notes.

CHANGELOG.rst

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,31 @@ CHANGELOG
1212
**[不兼容改动]**
1313

1414
- `get_position` 不能传入还未上市的 order_book_id,传入此类代码会抛出异常
15+
- `Instrument.listing_at` 更名为 `active_at`,旧方法移除
1516

16-
**[新增 API]**
17+
**[新增功能]**
18+
19+
- 新增 `active_instrument` / `active_instruments` API,用于获取当前交易时点正在活跃(上市中、未退市)的合约对象
20+
- 新增 `instrument_history`/ `instruments_history` API,用于获取合约历史记录列表(包含未上市或已退市合约)
21+
22+
**[重构和改善]**
23+
24+
- API 参数校验支持自动转换(Instrument/symbol -> order_book_id),统一下单与查询路径
25+
- 风控、下单、分析等模块统一基于交易时点的合约对象进行判断
26+
27+
**[其他改进]**
28+
29+
- 依赖调整:`typing-extensions` 版本提升至 `>=4.5.0`
30+
- 更新翻译文件
31+
32+
**[For Mod 开发者] 接口变更指引**
33+
34+
- `DataProxy` 增加合约历史/活跃合约相关接口(如 `get_active_instrument(s)`、`get_instrument_history`、`get_instruments_history` 等)
35+
- `Environment.get_instrument` 及 `DataProxy` 旧合约相关接口(因不符合新假设)标记为废弃
36+
- 参数校验体系调整
37+
- 新增 `assure_that` 支持参数自动转换(如 Instrument / symbol -> order_book_id),若 API 内部需要参数格式转换,推荐直接使用该功能将检验和转换合并
38+
- 移除部分依赖 “全局唯一 Instrument” 假设的校验规则,如 `is_valid_stock` / `is_valid_future` / `is_valid_instrument` 等
39+
- 新增 `is_valid_order_book_id` 用于 order_book_id 的基础合法性检验/转换
1740

1841

1942
6.0.0

docs/source/api/base_api.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,30 @@ instruments - 合约详细信息
325325
.. autofunction:: instruments
326326

327327

328+
active_instrument - 当前交易时点活跃合约
329+
------------------------------------------------------
330+
331+
.. autofunction:: active_instrument
332+
333+
334+
instrument_history - 合约历史记录
335+
------------------------------------------------------
336+
337+
.. autofunction:: instrument_history
338+
339+
340+
active_instruments - 批量活跃合约
341+
------------------------------------------------------
342+
343+
.. autofunction:: active_instruments
344+
345+
346+
instruments_history - 批量合约历史记录
347+
------------------------------------------------------
348+
349+
.. autofunction:: instruments_history
350+
351+
328352
history_bars - 某一合约历史 bar 数据
329353
------------------------------------------------------
330354

rqalpha/apis/api_base.py

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ def all_instruments(type=None, date=None):
610610
else:
611611
types = None
612612

613-
result = env.data_proxy.all_instruments(types, dt)
613+
result = env.data_proxy.get_all_instruments(types, dt)
614614
if types is not None and len(types) == 1:
615615
data = []
616616
for i in result:
@@ -641,6 +641,7 @@ def all_instruments(type=None, date=None):
641641
def instruments(id_or_symbols: Union[str, Iterable[str]]) -> Union[None, Instrument, List[Instrument]]:
642642
"""
643643
获取某个国家市场内一个或多个合约的详细信息。目前仅支持中国市场。
644+
若传入单个合约代码且存在多个历史合约,则返回合约列表;找不到合约时返回 None。
644645
645646
:param id_or_symbols: 合约代码或者合约代码列表
646647
@@ -702,12 +703,87 @@ def instruments(id_or_symbols: Union[str, Iterable[str]]) -> Union[None, Instrum
702703
EXECUTION_PHASE.AFTER_TRADING,
703704
EXECUTION_PHASE.SCHEDULED,
704705
)
705-
@apply_rules(assure_that("id_or_sym").is_active_instrument())
706-
def active_instrument(id_or_sym: Union[str, Instrument]) -> Instrument:
707-
return cast(Instrument, id_or_sym)
706+
@apply_rules(assure_that("order_book_id").is_active_instrument())
707+
def active_instrument(order_book_id: Union[str, Instrument]) -> Instrument:
708+
"""
709+
获取当前交易时点正在活跃(上市中、未退市)的合约对象。
710+
711+
:param order_book_id: 合约代码或 Instrument
712+
:return: 当前交易时点有效的 Instrument 对象
713+
:raises: 若合约在当前交易时点未上市或已退市,会抛出异常
714+
"""
715+
return cast(Instrument, order_book_id)
716+
717+
718+
@export_as_api
719+
@ExecutionContext.enforce_phase(
720+
EXECUTION_PHASE.ON_INIT,
721+
EXECUTION_PHASE.BEFORE_TRADING,
722+
EXECUTION_PHASE.OPEN_AUCTION,
723+
EXECUTION_PHASE.ON_BAR,
724+
EXECUTION_PHASE.ON_TICK,
725+
EXECUTION_PHASE.AFTER_TRADING,
726+
EXECUTION_PHASE.SCHEDULED,
727+
)
728+
@apply_rules(assure_that("order_book_id").is_valid_order_book_id())
729+
def instrument_history(order_book_id: str) -> list[Instrument]:
730+
"""
731+
获取指定合约的历史记录列表(包含未上市或已退市合约)。
732+
733+
:param order_book_id: 合约代码或 symbol
734+
:return: 合约历史列表,按上市时间排序
735+
"""
736+
return Environment.get_instance().data_proxy.get_instrument_history(order_book_id)
737+
738+
739+
@export_as_api
740+
@ExecutionContext.enforce_phase(
741+
EXECUTION_PHASE.ON_INIT,
742+
EXECUTION_PHASE.BEFORE_TRADING,
743+
EXECUTION_PHASE.OPEN_AUCTION,
744+
EXECUTION_PHASE.ON_BAR,
745+
EXECUTION_PHASE.ON_TICK,
746+
EXECUTION_PHASE.AFTER_TRADING,
747+
EXECUTION_PHASE.SCHEDULED,
748+
)
749+
@apply_rules(assure_that("order_book_ids").is_valid_oid_list())
750+
def active_instruments(
751+
order_book_ids: Union[str, Instrument, Iterable[str], Iterable[Instrument]]
752+
) -> dict[str, Instrument]:
753+
"""
754+
批量获取当前交易时点已上市的合约对象。
708755
756+
:param order_book_ids: 合约代码或 Instrument 的可迭代对象
757+
:return: order_book_id -> Instrument 的映射
758+
"""
759+
env = Environment.get_instance()
760+
dt = env.trading_dt
761+
return env.data_proxy.get_active_instruments(cast(list, order_book_ids), dt)
762+
763+
764+
@export_as_api
765+
@ExecutionContext.enforce_phase(
766+
EXECUTION_PHASE.ON_INIT,
767+
EXECUTION_PHASE.BEFORE_TRADING,
768+
EXECUTION_PHASE.OPEN_AUCTION,
769+
EXECUTION_PHASE.ON_BAR,
770+
EXECUTION_PHASE.ON_TICK,
771+
EXECUTION_PHASE.AFTER_TRADING,
772+
EXECUTION_PHASE.SCHEDULED,
773+
)
774+
@apply_rules(assure_that("order_book_ids").is_valid_oid_list())
775+
def instruments_history(
776+
order_book_ids: Union[str, Instrument, Iterable[str], Iterable[Instrument]]
777+
) -> list[Instrument]:
778+
"""
779+
批量获取合约历史记录列表(包含已退市合约)。
780+
781+
:param order_book_ids: 合约代码或 Instrument 的可迭代对象
782+
:return: 合约对象列表
783+
"""
784+
env = Environment.get_instance()
785+
return env.data_proxy.get_instruments_history(cast(list, order_book_ids))
709786

710-
# TODO: 提供 instrument_history, active_instruments, instruments_history 等 API
711787

712788
@export_as_api
713789
@apply_rules(
@@ -1002,4 +1078,3 @@ def repay(amount, account_type=DEFAULT_ACCOUNT_TYPE.STOCK):
10021078
env = Environment.get_instance()
10031079
return env.portfolio.finance_repay(amount * -1, account_type)
10041080

1005-

tests/integration_tests/test_api/test_api_base.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,70 @@ def handle_bar(context, _):
219219
run_func(config=__config__, init=init, handle_bar=handle_bar)
220220

221221

222+
def test_instrument_api():
223+
from rqalpha.utils.exception import RQInvalidArgument
224+
225+
config = {
226+
"base": {
227+
"start_date": "2019-01-02",
228+
"end_date": "2019-01-04",
229+
"frequency": "1d",
230+
"accounts": {
231+
"stock": 1000000,
232+
}
233+
},
234+
"extra": {
235+
"log_level": "error",
236+
},
237+
}
238+
239+
def init(context):
240+
context.active_stock = "000001.XSHE"
241+
context.inactive_stock = "000979.XSHE"
242+
243+
def handle_bar(context, _):
244+
active_ins = active_instrument(context.active_stock)
245+
assert active_ins.order_book_id == context.active_stock
246+
assert active_ins.active_at(context.now)
247+
248+
ins = instruments(context.active_stock)
249+
assert isinstance(ins, Instrument)
250+
assert ins.order_book_id == context.active_stock
251+
listed_date = ins.listed_date.date() if hasattr(ins.listed_date, "date") else ins.listed_date
252+
assert listed_date <= context.now.date()
253+
254+
ins_list = instruments([context.active_stock, context.inactive_stock])
255+
assert isinstance(ins_list, list)
256+
assert {i.order_book_id for i in ins_list} == {context.active_stock, context.inactive_stock}
257+
258+
active_history = instrument_history(context.active_stock)
259+
assert len(active_history) >= 1
260+
assert active_history == sorted(active_history, key=lambda i: i.listed_date)
261+
262+
inactive_history = instrument_history(context.inactive_stock)
263+
assert len(inactive_history) >= 1
264+
delisted_date = inactive_history[-1].de_listed_date
265+
assert delisted_date is not None
266+
delisted_date = delisted_date.date() if hasattr(delisted_date, "date") else delisted_date
267+
assert delisted_date < context.now.date()
268+
269+
active_map = active_instruments([context.active_stock, context.inactive_stock])
270+
assert context.active_stock in active_map
271+
assert context.inactive_stock not in active_map
272+
273+
history_list = instruments_history([context.active_stock, context.inactive_stock])
274+
assert {i.order_book_id for i in history_list} == {context.active_stock, context.inactive_stock}
275+
276+
try:
277+
active_instrument(context.inactive_stock)
278+
except RQInvalidArgument:
279+
pass
280+
else:
281+
raise AssertionError("inactive instrument should raise RQInvalidArgument")
282+
283+
run_func(config=config, init=init, handle_bar=handle_bar)
284+
285+
222286
def test_sector():
223287
def handle_bar(_, __):
224288
assert len(sector('金融')) >= 80, "sector('金融') 返回结果少于 80 个"

0 commit comments

Comments
 (0)