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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "akquant"
version = "0.1.35"
version = "0.1.36"
edition = "2024"
description = "High-performance quantitative trading framework based on Rust and Python"
license = "MIT"
Expand Down
115 changes: 93 additions & 22 deletions examples/01_quickstart.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,112 @@
from typing import Any

import akquant as aq
import akshare as ak
import pandas as pd
from akquant import Bar, Strategy
from akquant.config import BacktestConfig, RiskConfig, StrategyConfig

# 1. 准备数据
# 使用 akshare 获取 A 股历史数据 (需安装: pip install akshare)
df = ak.stock_zh_a_daily(symbol="sh600000", start_date="20230101", end_date="20231231")
df_1 = ak.stock_zh_a_daily(
symbol="sh600000", start_date="20000101", end_date="20261231"
)
df_1["symbol"] = "600000"
df_2 = ak.stock_zh_a_daily(
symbol="sh600004", start_date="20000101", end_date="20261231"
)
df_2["symbol"] = "600004"
df_3 = ak.stock_zh_a_daily(
symbol="sh600006", start_date="20000101", end_date="20261231"
)
df_3["symbol"] = "600006"
df = {"600000": df_1, "600004": df_2, "600006": df_3}


class MyStrategy(Strategy):
"""Simple demo strategy."""
"""
Example strategy for testing broker execution.

This strategy buys on the first bar and holds for 100 bars or until 10% profit.
"""

def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize strategy state."""
super().__init__()
self.bars_held: dict[str, int] = {}
self.entry_prices: dict[str, float] = {}

def on_bar(self, bar: Bar) -> None:
"""Execute on every bar."""
# 简单策略示例:
# 当收盘价 > 开盘价 (阳线) -> 买入
# 当收盘价 < 开盘价 (阴线) -> 卖出
"""
Handle bar data event.

# 获取当前持仓
current_pos = self.get_position(bar.symbol)
:param bar: The current bar data
"""
symbol = bar.symbol
pos = self.get_position(symbol)

if current_pos == 0 and bar.close > bar.open:
self.buy(bar.symbol, 100)
# print(f"[{bar.timestamp_str}] Buy 100 at {bar.close:.2f}")
# 维护持仓计数
if pos > 0:
if symbol not in self.bars_held:
self.bars_held[symbol] = 0
self.bars_held[symbol] += 1
else:
# 如果没有持仓,清理状态
if symbol in self.bars_held:
del self.bars_held[symbol]
if symbol in self.entry_prices:
del self.entry_prices[symbol]

elif current_pos > 0 and bar.close < bar.open:
self.close_position(bar.symbol)
# print(f"[{bar.timestamp_str}] Sell 100 at {bar.close:.2f}")
# 交易逻辑
if pos == 0:
# 简单示例:每个标的买入 33% 仓位
self.order_target_percent(target_percent=0.33, symbol=symbol)
# 初始化计数器 (虽然会在下个 bar 的 pos>0 分支中自增,但这里先占位)
self.bars_held[symbol] = 0
self.entry_prices[symbol] = bar.close

elif pos > 0:
entry_price = self.entry_prices.get(symbol, bar.close)
current_bars_held = self.bars_held.get(symbol, 0)

# 运行回测
result = aq.run_backtest(
data=df, strategy=MyStrategy, symbol="sh600000", initial_cash=100000
# 计算收益率
pnl_pct = (bar.close - entry_price) / entry_price

# 止盈条件:收益率 >= 10%
if pnl_pct >= 0.10:
self.sell(symbol, pos)
print(
f"Take Profit Triggered for {symbol}: Entry={entry_price}, "
f"Current={bar.close}, PnL={pnl_pct:.2%}"
)
# 持仓时间条件:持有满 100 个 Bar
elif current_bars_held >= 100:
self.close_position()


# 配置风险参数:safety_margin
risk_config = RiskConfig(safety_margin=0.0001)
strategy_config = StrategyConfig(risk=risk_config)
backtest_config = BacktestConfig(
strategy_config=strategy_config,
)

result = aq.run_backtest(
strategy=MyStrategy,
data=df,
initial_cash=5000000,
commission_rate=0.0,
stamp_tax_rate=0.0,
transfer_fee_rate=0.0,
min_commission=5.0,
lot_size=1,
execution_mode=aq.ExecutionMode.NextAverage,
config=backtest_config,
start_time="20250101",
end_time="20250105",
symbol=["600000", "600004", "600006"],
)

pd.set_option("display.max_columns", None) # 显示所有行
# 打印回测结果
print("\n=== Backtest Result ===")
pd.set_option("display.max_columns", None)
pd.set_option("display.max_rows", None)
print(result)
print(result.orders_df)
# print(result.equity_curve)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "maturin"

[project]
name = "akquant"
version = "0.1.35"
version = "0.1.36"
description = "High-performance quantitative trading framework based on Rust and Python"
readme = "README.md"
license = {text = "MIT License"}
Expand Down
27 changes: 23 additions & 4 deletions python/akquant/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -904,9 +904,14 @@ def cancel_order(self, order_id: str) -> None:
if self.ctx:
self.ctx.cancel_order(order_id)

def cancel_all_orders(self) -> None:
"""取消当前所有未完成的订单."""
for order in self.get_open_orders():
def cancel_all_orders(self, symbol: Optional[str] = None) -> None:
"""
取消当前所有未完成的订单.

Args:
symbol: 标的代码 (如果不填, 取消所有标的订单)
"""
for order in self.get_open_orders(symbol=symbol):
self.cancel_order(order.id)

def buy(
Expand Down Expand Up @@ -1171,7 +1176,7 @@ def order_target_value(

# 1. Cancel existing open orders for this symbol
# This prevents "stacking" orders and ensures we target the net exposure
self.cancel_all_orders()
self.cancel_all_orders(symbol=symbol)

# 2. Get Price
if price is not None:
Expand Down Expand Up @@ -1200,6 +1205,20 @@ def order_target_value(
target_qty = target_value / current_price
delta_qty = target_qty - current_qty

# 3. 整手调整 (向下取整到 lot_size 倍数)
current_lot_size = 1
if isinstance(self.lot_size, int):
current_lot_size = self.lot_size
elif isinstance(self.lot_size, dict):
val = self.lot_size.get(symbol, self.lot_size.get("DEFAULT", 1))
current_lot_size = int(val) if val is not None else 1

if current_lot_size > 0:
if delta_qty > 0:
delta_qty = (delta_qty // current_lot_size) * current_lot_size
elif delta_qty < 0:
delta_qty = -((abs(delta_qty) // current_lot_size) * current_lot_size)

# 自动调整买入数量,防止资金不足
if delta_qty > 0 and self.ctx:
max_buy_qty = self._calculate_max_buy_qty(
Expand Down
4 changes: 2 additions & 2 deletions src/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,15 +480,15 @@ impl ExecutionClient for RealtimeExecutionClient {
true
}

fn on_order(&mut self, order: Order) {
fn on_order(&mut self, _order: Order) {
// println!(
// "[Realtime] Sending Order to Broker: {} {:?} {}",
// order.symbol, order.side, order.quantity
// );
// In real impl, send to broker API
}

fn on_cancel(&mut self, order_id: &str) {
fn on_cancel(&mut self, _order_id: &str) {
// println!("[Realtime] Cancelling Order: {}", order_id);
}

Expand Down