| name | ptrade |
| description | Ptrade Hundsun Quantitative Trading Platform — Strategies run on broker servers with low-latency execution, supporting A-shares, futures, margin trading, and other China securities markets. |
| homepage | https://ptradeapi.com |
Ptrade (Hundsun Quantitative Trading Platform)
Ptrade is a professional quantitative trading platform developed by Hundsun Electronics. Strategies run on broker servers (intranet) for low-latency execution. It uses an event-driven Python strategy framework.
⚠️ Requires broker Ptrade access authorization. Strategies run on the broker's cloud — no external network access. Cannot install pip packages; only built-in third-party libraries are available.
Supported Markets & Business Types
Backtesting support:
- Regular stock trading (unit: shares)
- Convertible bond trading (unit: lots, T+0)
- Margin trading collateral buy/sell (unit: shares)
- Futures speculative trading (unit: contracts, T+0)
- LOF fund trading (unit: shares)
- ETF fund trading (unit: shares)
Live trading support:
- Regular stock trading (unit: shares)
- Convertible bond trading (T+0)
- Margin trading (unit: shares)
- ETF creation/redemption, arbitrage (unit: shares)
- Treasury reverse repo (unit: shares)
- Futures speculative trading (unit: contracts, T+0)
- ETF fund trading (unit: shares)
Level2 10-level market data supported by default. Some brokers provide free L2 tick-by-tick data.
Price Precision Rules
| Asset Type | Minimum Tick | Decimal Places |
|---|
| Stocks | 0.01 | 2 |
| Convertible Bonds | 0.001 | 3 |
| LOF / ETF | 0.001 | 3 |
| Treasury Reverse Repo | 0.005 | 3 |
| Stock Index Futures | 0.2 | 1 |
| Treasury Bond Futures | 0.005 | 3 |
| ETF Options | 0.0001 | 4 |
⚠️ When placing orders with limit_price, the price must conform to the correct decimal precision, otherwise the order will be rejected.
Stock Code Format
- Shanghai:
600570.SS
- Shenzhen:
000001.SZ
- Index:
000300.SS (CSI 300)
Strategy Lifecycle (Event-Driven)
def initialize(context):
"""Required — Called once at startup. Used to set stock pool, benchmark, and scheduled tasks."""
g.security = '600570.SS'
set_universe(g.security)
def before_trading_start(context, data):
"""Optional — Called before market open.
Backtest mode: Executes at 8:30 each trading day.
Live mode: Executes immediately on first start, then at 9:10 daily (default, broker-configurable)."""
log.info('Pre-market preparation')
def handle_data(context, data):
"""Required — Triggered on each bar.
Daily mode: Executes once at 14:50 daily (default).
Minute mode: Executes at each minute bar close.
data[sid] provides: open, high, low, close, price, volume, money."""
current_price = data[g.security]['close']
cash = context.portfolio.cash
def after_trading_end(context, data):
"""Optional — Called at 15:30 after market close."""
log.info('Trading day ended')
def tick_data(context, data):
"""Optional — Triggered every 3 seconds during market hours (9:30-14:59, live only).
Must use order_tick() to place orders in this function.
data format: {stock_code: {'order': DataFrame/None, 'tick': DataFrame, 'transcation': DataFrame/None}}"""
for stock, d in data.items():
tick = d['tick']
price = tick['last_px']
bid1 = tick['bid_grp'][1]
ask1 = tick['offer_grp'][1]
log.info(f'{stock}: {price}, upper_limit={tick["up_px"]}, lower_limit={tick["down_px"]}')
order_data = d['order']
trans_data = d['transcation']
def on_order_response(context, order_list):
"""Optional — Triggered on order status changes (faster than get_orders).
order_list is a list of dicts containing: entrust_no, stock_code, amount, price,
business_amount, status, order_id, entrust_type, entrust_prop, error_info, order_time."""
for o in order_list:
log.info(f'Order {o["stock_code"]}: status={o["status"]}, filled={o["business_amount"]}/{o["amount"]}')
def on_trade_response(context, trade_list):
"""Optional — Triggered on trade execution (faster than get_trades).
trade_list is a list of dicts containing: entrust_no, stock_code, business_amount,
business_price, business_balance, business_id, status, order_id, entrust_bs, business_time.
Note: status=9 means rejected order."""
for t in trade_list:
direction = 'Buy' if t['entrust_bs'] == '1' else 'Sell'
log.info(f'{direction} {t["stock_code"]}: {t["business_amount"]}@{t["business_price"]}')
Strategy Execution Frequency
| Mode | Frequency | Execution Time |
|---|
| Daily | Once per day | Backtest: 15:00, Live: 14:50 (configurable) |
| Minute | Once per minute | At each minute bar close |
| Tick | Every 3 seconds | 9:30–14:59, via tick_data or run_interval |
Time Reference
| Phase | Time | Available Functions |
|---|
| Pre-market | Before 9:30 | before_trading_start, run_daily(time='09:15') |
| Market hours | 9:30–15:00 | handle_data, run_daily, run_interval, tick_data |
| Post-market | 15:30 | after_trading_end, run_daily(time='15:10') |
Initialization Setup Functions (Use Only in initialize)
Stock Pool & Benchmark
def initialize(context):
set_universe(['600570.SS', '000001.SZ'])
set_benchmark('000300.SS')
Commission & Slippage (Backtest Only)
def initialize(context):
set_commission(PerTrade(buy_cost=0.0003, sell_cost=0.0013, unit='perValue', min_cost=5))
set_slippage(FixedSlippage(0.02))
set_volume_ratio(0.025)
set_limit_mode(0)
Scheduled Tasks
def initialize(context):
run_daily(context, my_morning_task, time='09:31')
run_daily(context, my_afternoon_task, time='14:50')
run_interval(context, my_tick_handler, seconds=10)
Strategy Parameters (Configurable from UI)
def initialize(context):
set_parameters(
context,
ma_fast=5,
ma_slow=20,
position_ratio=0.95
)
Data Functions
get_history — Get Recent N Bars
get_history(count, frequency='1d', field='close', security_list=None, fq=None, include=False, fill='nan', is_dict=False)
df = get_history(20, '1d', ['open', 'high', 'low', 'close', 'volume'], '600570.SS', fq='pre')
get_price — Query by Date Range
get_price(security, start_date=None, end_date=None, frequency='1d', fields=None, fq=None, count=None, is_dict=False)
df = get_price('600570.SS', start_date='20240101', end_date='20240630', frequency='1d',
fields=['open', 'high', 'low', 'close', 'volume'])
df = get_price('600570.SS', end_date='20240630', frequency='1d', count=20)
df = get_price(['600570.SS', '000001.SZ'], start_date='20240101', end_date='20240630')
df = get_price('600570.SS', start_date='2024-06-01 09:30', end_date='2024-06-01 15:00', frequency='5m')
⚠️ get_history and get_price cannot be called concurrently from different threads (e.g., when run_daily and handle_data execute simultaneously).
get_snapshot — Real-time Market Snapshot (Live Only)
snapshot = get_snapshot('600570.SS')
snapshots = get_snapshot(['600570.SS', '000001.SZ'])
price = snapshots['600570.SS']['last_px']
get_gear_price — Order Book Depth (Live Only)
gear = get_gear_price('600570.SS')
bid1_price, bid1_vol, bid1_count = gear['bid_grp'][1]
ask1_price, ask1_vol, ask1_count = gear['offer_grp'][1]
gears = get_gear_price(['600570.SS', '000001.SZ'])
Level2 Data (Requires L2 Access)
entrust = get_individual_entrust(
stocks=['600570.SS'],
data_count=50,
start_pos=0,
search_direction=1,
is_dict=False
)
transaction = get_individual_transaction(
stocks=['600570.SS'],
data_count=50,
is_dict=False
)
Stock & Reference Data
Basic Information
name = get_stock_name('600570.SS')
info = get_stock_info('600570.SS')
status = get_stock_status('600570.SS')
exrights = get_stock_exrights('600570.SS')
blocks = get_stock_blocks('600570.SS')
stocks = get_index_stocks('000300.SS')
stocks = get_industry_stocks('银行')
stocks = get_Ashares()
etfs = get_etf_list()
Convertible Bond Data
cb_codes = get_cb_list()
cb_info = get_cb_info()
Financial Data
get_fundamentals(security, table, fields=None, date=None, start_year=None, end_year=None,
report_types=None, date_type=None, merge_type=None)
df = get_fundamentals('600570.SS', 'balance_statement', 'total_assets', date='20240630')
df = get_fundamentals('600570.SS', 'income_statement', fields=['revenue', 'net_profit'],
start_year='2022', end_year='2024')
⚠️ Rate limit: Max 100 calls per second, max 500 records per call. Add sleep for batch queries.
Trading Calendar
today = get_trading_day()
all_days = get_all_trades_days()
days = get_trade_days('2024-01-01', '2024-06-30')
Trading Functions
order — Buy/Sell by Quantity
order(security, amount, limit_price=None)
order('600570.SS', 100)
order('600570.SS', 100, limit_price=39.0)
order('600570.SS', -500)
order('131810.SZ', -10)
order_target — Adjust to Target Quantity
order_target('600570.SS', 1000)
order_target('600570.SS', 0)
order_value — Buy by Value
order_value('600570.SS', 100000)
order_target_value — Adjust to Target Value
order_target_value('600570.SS', 200000)
order_market — Market Order Types (Live Only)
order_market(security, amount, market_type, limit_price=None)
order_market('600570.SS', 100, 0, limit_price=35.0)
order_market('000001.SZ', 100, 4)
⚠️ Shanghai stocks require limit_price when using order_market. Convertible bonds are not supported.
order_tick — Tick-Triggered Order (Use Only in tick_data)
def tick_data(context, data):
order_tick('600570.SS', 100, limit_price=39.0)
cancel_order — Cancel Order
cancel_order(order_id)
cancel_order_ex(order_id)
IPO Subscription
ipo_stocks_order()
After-Hours Fixed Price Order
after_trading_order('600570.SS', 100)
after_trading_cancel_order(order_id)
ETF Operations
etf_basket_order('510050.SS', 1,
price_style='S3',
position=True,
info={'600000.SS': {'cash_replace_flag': 1, 'position_replace_flag': 1, 'limit_price': 12}})
etf_purchase_redemption('510050.SS', 900000)
etf_purchase_redemption('510050.SS', -900000)
Query Functions
Position Query
pos = get_position('600570.SS')
positions = get_positions(['600570.SS', '000001.SZ'])
Order Query
open_orders = get_open_orders()
order = get_order(order_id)
orders = get_orders()
all_orders = get_all_orders()
trades = get_trades()
Account Information (via context)
context.portfolio.cash
context.portfolio.total_value
context.portfolio.positions_value
context.portfolio.positions
context.capital_base
context.previous_date
context.blotter.current_dt
Margin Trading
Trading Operations
margin_trade('600570.SS', 1000, limit_price=39.0)
margincash_open('600570.SS', 1000, limit_price=39.0)
margincash_close('600570.SS', 1000, limit_price=40.0)
margincash_direct_refund(amount=100000)
marginsec_open('600570.SS', 1000, limit_price=40.0)
marginsec_close('600570.SS', 1000, limit_price=39.0)
marginsec_direct_refund('600570.SS', 1000)
Query Operations
cash_stocks = get_margincash_stocks()
sec_stocks = get_marginsec_stocks()
contract = get_margin_contract()
margin_asset = get_margin_assert()
assure_list = get_assure_security_list()
max_buy = get_margincash_open_amount('600570.SS')
max_sell = get_margincash_close_amount('600570.SS')
max_short = get_marginsec_open_amount('600570.SS')
max_cover = get_marginsec_close_amount('600570.SS')
Futures Trading
Trading Operations
buy_open('IF2401.CFX', 1, limit_price=3500.0)
sell_close('IF2401.CFX', 1, limit_price=3550.0)
sell_open('IF2401.CFX', 1, limit_price=3550.0)
buy_close('IF2401.CFX', 1, limit_price=3500.0)
Query & Settings (Backtest)
margin_rate = get_margin_rate('IF2401.CFX')
instruments = get_instruments('IF2401.CFX')
set_future_commission(0.000023)
set_margin_rate('IF2401.CFX', 0.15)
Built-in Technical Indicators
macd = get_MACD('600570.SS', N1=12, N2=26, M=9)
kdj = get_KDJ('600570.SS', N=9, M1=3, M2=3)
rsi = get_RSI('600570.SS', N=14)
cci = get_CCI('600570.SS', N=14)
Utility Functions
log.info('message')
is_trade('600570.SS')
check_limit('600570.SS')
freq = get_frequency()
Notification Functions
send_email(context, subject='Signal', content='Buy 600570', to_address='you@email.com')
send_qywx(context, msg='Buy signal triggered')
Global Objects & Context
g.my_var = 100
g.stock_list = ['600570.SS', '000001.SZ']
g.__my_class_instance = SomeClass()
context.portfolio.cash
context.portfolio.total_value
context.portfolio.positions_value
context.portfolio.positions
context.capital_base
context.previous_date
context.blotter.current_dt
Persistence Mechanism
Ptrade automatically serializes and saves the g object using pickle after before_trading_start, handle_data, and after_trading_end execute. On restart, initialize runs first, then persisted data is restored.
Custom persistence example:
import pickle
NOTEBOOK_PATH = get_research_path()
def initialize(context):
try:
with open(NOTEBOOK_PATH + 'hold_days.pkl', 'rb') as f:
g.hold_days = pickle.load(f)
except:
g.hold_days = {}
g.security = '600570.SS'
set_universe(g.security)
def handle_data(context, data):
with open(NOTEBOOK_PATH + 'hold_days.pkl', 'wb') as f:
pickle.dump(g.hold_days, f, -1)
⚠️ IO objects (open files, class instances) cannot be serialized. Use g.__private_var (double underscore prefix) for non-serializable objects.
Strategy Examples
Example 1: Call Auction Limit-Up Chasing
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)
run_daily(context, aggregate_auction_func, time='9:23')
def aggregate_auction_func(context):
stock = g.security
snapshot = get_snapshot(stock)
price = snapshot[stock]['last_px']
up_limit = snapshot[stock]['up_px']
if float(price) >= float(up_limit):
order(g.security, 100, limit_price=up_limit)
def handle_data(context, data):
pass
Example 2: Tick-Level Moving Average Strategy
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)
run_interval(context, func, seconds=3)
def before_trading_start(context, data):
history = get_history(10, '1d', 'close', g.security, fq='pre', include=False)
g.close_array = history['close'].values
def func(context):
stock = g.security
snapshot = get_snapshot(stock)
price = snapshot[stock]['last_px']
ma5 = (g.close_array[-4:].sum() + price) / 5
ma10 = (g.close_array[-9:].sum() + price) / 10
cash = context.portfolio.cash
if ma5 > ma10:
order_value(stock, cash)
log.info('Buy %s' % stock)
elif ma5 < ma10 and get_position(stock).amount > 0:
order_target(stock, 0)
log.info('Sell %s' % stock)
def handle_data(context, data):
pass
Example 3: Dual Moving Average Strategy
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)
def handle_data(context, data):
security = g.security
df = get_history(20, '1d', 'close', security, fq=None, include=False)
ma5 = df['close'][-5:].mean()
ma20 = df['close'][-20:].mean()
current_price = data[security]['close']
cash = context.portfolio.cash
position = get_position(security)
if current_price > 1.01 * ma20 and position.amount == 0:
order_value(security, cash * 0.95)
log.info(f'Buy {security}')
elif current_price < ma5 and position.amount > 0:
order_target(security, 0)
log.info(f'Sell {security}')
Example 4: After-Hours Reverse Repo + IPO Subscription
def initialize(context):
g.security = '131810.SZ'
set_universe(g.security)
run_daily(context, reverse_repo, time='14:50')
run_daily(context, ipo_subscribe, time='09:31')
def reverse_repo(context):
cash = context.portfolio.cash
lots = int(cash / 1000) * 10
if lots >= 10:
order(g.security, -lots)
log.info(f'Reverse repo: {lots} lots')
def ipo_subscribe(context):
ipo_stocks_order()
log.info('IPO subscription submitted')
def handle_data(context, data):
pass
Order Status Codes
| Status Code | Description |
|---|
| 0 | Not submitted |
| 1 | Pending submission |
| 2 | Submitted |
| 5 | Partially filled |
| 6 | Fully filled (backtest) |
| 7 | Partially cancelled |
| 8 | Fully filled (live) |
| 9 | Rejected |
| a | Cancelled |
Usage Tips
- Strategies run on broker intranet servers — no external network access, cannot
pip install.
- Use
g (global object) to persist variables across functions. Variables prefixed with __ will not be persisted.
- Built-in third-party libraries include: pandas, numpy, talib, scipy, sklearn, etc.
handle_data execution frequency depends on strategy period setting (tick/1m/5m/1d, etc.).
- Backtest and live trading use the same code —
set_commission/set_slippage only take effect in backtesting.
- Always add exception handling (
try/except) in trading strategies to prevent unexpected termination.
get_history and get_price cannot be called concurrently from different threads.
- When using limit orders, ensure price decimal precision matches the asset type.
- When multiple strategies run concurrently, callback events are independent of each other.
- Use
get_research_path() for file I/O (CSV, pickle files).
- Documentation: https://ptradeapi.com
- QMT API Documentation: http://qmt.ptradeapi.com
Advanced Examples
Tick-Level Volume-Price Strategy — Large Order Tracking
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)
g.big_order_threshold = 500000
g.buy_signal_count = 0
g.sell_signal_count = 0
g.signal_window = 10
def tick_data(context, data):
"""Triggered every 3 seconds, analyzes tick-by-tick trade data"""
stock = g.security
if stock not in data:
return
tick = data[stock]['tick']
trans = data[stock].get('transcation', None)
last_price = tick['last_px']
pre_close = tick['preclose_px']
change_pct = (last_price - pre_close) / pre_close * 100
if trans is not None and len(trans) > 0:
for _, row in trans.iterrows():
amount = row['hq_px'] * row['business_amount']
if amount >= g.big_order_threshold:
direction = 'Buy' if row['business_direction'] == 1 else 'Sell'
log.info(f'Large {direction} order: {amount/10000:.1f}0k CNY @ {row["hq_px"]}')
if row['business_direction'] == 1:
g.buy_signal_count += 1
else:
g.sell_signal_count += 1
position = get_position(stock)
cash = context.portfolio.cash
if g.buy_signal_count >= g.signal_window and position.amount == 0:
order_value(stock, cash * 0.9)
log.info(f'Large order tracking buy: accumulated {g.buy_signal_count} large buy orders')
g.buy_signal_count = 0
g.sell_signal_count = 0
elif g.sell_signal_count >= g.signal_window and position.amount > 0:
order_target(stock, 0)
log.info(f'Large order tracking sell: accumulated {g.sell_signal_count} large sell orders')
g.buy_signal_count = 0
g.sell_signal_count = 0
def handle_data(context, data):
pass
def after_trading_end(context, data):
g.buy_signal_count = 0
g.sell_signal_count = 0
log.info('Signal counts reset')
ETF Arbitrage Strategy — Premium/Discount Monitoring & Trading
def initialize(context):
g.etf_code = '510050.SS'
set_universe(g.etf_code)
g.premium_threshold = 0.005
g.discount_threshold = -0.005
g.min_unit = 900000
run_interval(context, check_premium, seconds=10)
def check_premium(context):
"""Check ETF premium/discount rate and execute arbitrage"""
etf = g.etf_code
snapshot = get_snapshot(etf)
if etf not in snapshot:
return
etf_price = snapshot[etf]['last_px']
nav_estimate = snapshot[etf].get('iopv', etf_price)
if nav_estimate <= 0 or etf_price <= 0:
return
premium_rate = (etf_price - nav_estimate) / nav_estimate
log.info(f'ETF price={etf_price:.4f}, NAV={nav_estimate:.4f}, premium/discount={premium_rate*100:.3f}%')
position = get_position(etf)
cash = context.portfolio.cash
if premium_rate > g.premium_threshold:
if cash > nav_estimate * g.min_unit:
etf_basket_order(etf, 1, price_style='S1', position=True)
log.info(f'Premium arbitrage: create ETF basket, premium rate={premium_rate*100:.3f}%')
elif premium_rate < g.discount_threshold:
if cash > etf_price * g.min_unit:
order(etf, g.min_unit, limit_price=etf_price)
log.info(f'Discount arbitrage: buy ETF, discount rate={premium_rate*100:.3f}%')
def handle_data(context, data):
pass
Convertible Bond T+0 Intraday Trading Strategy
def initialize(context):
g.cb_list = []
g.intraday_profit = 0.003
g.stop_loss = -0.005
g.max_hold_value = 100000
set_universe(['110059.SS'])
run_interval(context, intraday_trade, seconds=10)
def before_trading_start(context, data):
cb_info = get_cb_info()
if cb_info is not None and len(cb_info) > 0:
filtered = cb_info[cb_info['premium_rate'] < 20]
g.cb_list = filtered['bond_code'].tolist()[:10]
log.info(f'Today\'s CB pool: {len(g.cb_list)} bonds')
def intraday_trade(context):
"""Intraday T+0 trading logic"""
for cb_code in g.cb_list[:5]:
try:
snapshot = get_snapshot(cb_code)
if cb_code not in snapshot:
continue
price = snapshot[cb_code]['last_px']
pre_close = snapshot[cb_code]['preclose_px']
change_pct = (price - pre_close) / pre_close if pre_close > 0 else 0
position = get_position(cb_code)
hold_amount = position.amount if position else 0
if hold_amount > 0:
cost = position.cost_basis
pnl = (price - cost) / cost if cost > 0 else 0
if pnl >= g.intraday_profit:
order_target(cb_code, 0)
log.info(f'CB take profit: {cb_code} profit {pnl*100:.2f}%')
elif pnl <= g.stop_loss:
order_target(cb_code, 0)
log.info(f'CB stop loss: {cb_code} loss {pnl*100:.2f}%')
else:
if -0.01 < change_pct < 0:
buy_value = min(g.max_hold_value, context.portfolio.cash * 0.2)
if buy_value > 1000:
order_value(cb_code, buy_value)
log.info(f'CB buy: {cb_code} @ {price:.3f}')
except Exception as e:
log.error(f'CB trading error: {cb_code}, {str(e)}')
def handle_data(context, data):
pass
Scheduled Task Comprehensive Strategy — Pre-Market Selection + Intraday Trading + Post-Market Summary
import pickle
NOTEBOOK_PATH = get_research_path()
def initialize(context):
g.stock_pool = []
g.traded_today = False
set_universe(['000300.SS'])
run_daily(context, morning_select, time='09:25')
run_daily(context, morning_trade, time='09:35')
run_daily(context, noon_check, time='13:05')
run_daily(context, afternoon_close, time='14:50')
def morning_select(context):
"""Pre-market stock selection: screen stocks based on previous day's data"""
hs300 = get_index_stocks('000300.SS')
candidates = []
for stock in hs300[:50]:
try:
df = get_history(20, '1d', ['close', 'volume'], stock, fq='pre', include=False)
if len(df) < 20:
continue
close = df['close'].values
volume = df['volume'].values
ma5 = close[-5:].mean()
ma20 = close.mean()
if ma5 <= ma20:
continue
vol_5d = volume[-5:].mean()
vol_20d = volume.mean()
if vol_5d / vol_20d < 1.2:
continue
if abs(close[-1] - ma20) / ma20 > 0.05:
continue
candidates.append({
'stock': stock,
'ma5': ma5,
'ma20': ma20,
'vol_ratio': vol_5d / vol_20d
})
except:
continue
candidates.sort(key=lambda x: x['vol_ratio'], reverse=True)
g.stock_pool = [c['stock'] for c in candidates[:5]]
g.traded_today = False
log.info(f'Pre-market selection complete: {g.stock_pool}')
def morning_trade(context):
"""Opening trade: buy selected stocks"""
if g.traded_today or not g.stock_pool:
return
cash = context.portfolio.cash
per_stock_value = cash * 0.9 / len(g.stock_pool)
for stock in g.stock_pool:
try:
if not is_trade(stock):
continue
status = check_limit(stock)
if status == 1:
continue
order_value(stock, per_stock_value)
log.info(f'Buy: {stock}, value={per_stock_value:.0f}')
except Exception as e:
log.error(f'Buy error: {stock}, {str(e)}')
g.traded_today = True
def noon_check(context):
"""Midday check: stop loss and exception handling"""
positions = context.portfolio.positions
for stock, pos in positions.items():
if pos.amount <= 0:
continue
pnl = (pos.last_sale_price - pos.cost_basis) / pos.cost_basis if pos.cost_basis > 0 else 0
if pnl < -0.03:
order_target(stock, 0)
log.info(f'Midday stop loss: {stock}, loss={pnl*100:.2f}%')
def afternoon_close(context):
"""End-of-day operations: summarize today's P&L"""
total_value = context.portfolio.total_value
cash = context.portfolio.cash
positions = context.portfolio.positions
log.info(f'=== End-of-Day Summary ===')
log.info(f'Total assets: {total_value:.2f}')
log.info(f'Available cash: {cash:.2f}')
log.info(f'Number of positions: {len([p for p in positions.values() if p.amount > 0])}')
for stock, pos in positions.items():
if pos.amount > 0:
pnl = (pos.last_sale_price - pos.cost_basis) / pos.cost_basis * 100
log.info(f' {stock}: {pos.amount} shares, cost={pos.cost_basis:.2f}, '
f'price={pos.last_sale_price:.2f}, P&L={pnl:.2f}%')
try:
with open(NOTEBOOK_PATH + 'trade_log.pkl', 'rb') as f:
trade_log = pickle.load(f)
except:
trade_log = []
trade_log.append({
'date': str(context.blotter.current_dt),
'total_value': total_value,
'cash': cash,
'stock_pool': g.stock_pool
})
with open(NOTEBOOK_PATH + 'trade_log.pkl', 'wb') as f:
pickle.dump(trade_log, f, -1)
def handle_data(context, data):
pass
Multi-Strategy Parallel — MACD + KDJ Dual Signal Confirmation
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)
def handle_data(context, data):
stock = g.security
df = get_history(60, '1d', ['open', 'high', 'low', 'close', 'volume'], stock, fq='pre')
if len(df) < 60:
return
close = df['close'].values
high = df['high'].values
low = df['low'].values
macd = get_MACD(stock, N1=12, N2=26, M=9)
dif = macd['DIF']
dea = macd['DEA']
macd_hist = macd['MACD']
kdj = get_KDJ(stock, N=9, M1=3, M2=3)
k_value = kdj['K']
d_value = kdj['D']
j_value = kdj['J']
rsi = get_RSI(stock, N=14)
rsi_value = rsi['RSI']
position = get_position(stock)
cash = context.portfolio.cash
current_price = data[stock]['close']
macd_golden = dif > dea
kdj_golden = k_value > d_value and j_value < 80
rsi_normal = 30 < rsi_value < 70
if macd_golden and kdj_golden and rsi_normal and position.amount == 0:
order_value(stock, cash * 0.95)
log.info(f'Buy signal: MACD golden cross + KDJ golden cross + RSI normal, DIF={dif:.2f}, K={k_value:.1f}, RSI={rsi_value:.1f}')
elif position.amount > 0:
if dif < dea or j_value > 100 or rsi_value > 80:
reason = []
if dif < dea: reason.append('MACD death cross')
if j_value > 100: reason.append(f'KDJ overbought J={j_value:.1f}')
if rsi_value > 80: reason.append(f'RSI overbought={rsi_value:.1f}')
order_target(stock, 0)
log.info(f'Sell signal: {"+".join(reason)}')
社区与支持
由 大佬量化 (Boss Quant) 维护 — 量化交易教学与策略研发团队。
微信客服: bossquant1 · Bilibili · 搜索 大佬量化 on 微信公众号 / Bilibili / 抖音