Add files via upload

This commit is contained in:
xiaochuan
2025-11-14 22:56:44 +00:00
committed by GitHub
parent cf1944a688
commit ef6e1e278d
96 changed files with 3300 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# core.strategy package init
from . import base
from . import rsi_mean_reversion
+122
View File
@@ -0,0 +1,122 @@
from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Any
from datetime import datetime
import pandas as pd
from ..data.base import MarketDataEvent
class Position:
"""持仓类,表示当前市场头寸"""
def __init__(
self,
instrument: str,
direction: str, # "LONG" or "SHORT"
size: float,
entry_price: float,
entry_time: datetime,
stop_loss: Optional[float] = None,
take_profit: Optional[float] = None
):
self.instrument = instrument
self.direction = direction
self.size = size
self.entry_price = entry_price
self.entry_time = entry_time
self.stop_loss = stop_loss
self.take_profit = take_profit
self.unrealized_pnl = 0.0
self.realized_pnl = 0.0
class SignalEvent:
"""交易信号事件"""
def __init__(
self,
instrument: str,
timestamp: datetime,
signal_type: str, # "LONG", "SHORT", "EXIT"
direction: str,
strength: float = 1.0,
stop_loss: Optional[float] = None,
take_profit: Optional[float] = None
):
self.event_type = "SIGNAL"
self.instrument = instrument
self.timestamp = timestamp
self.signal_type = signal_type
self.direction = direction
self.strength = strength
self.stop_loss = stop_loss
self.take_profit = take_profit
class Strategy(ABC):
"""
策略基类
定义了策略开发的标准接口
"""
def __init__(
self,
instrument: str,
position_size: float = 1.0,
max_positions: int = 1
):
self.instrument = instrument
self.position_size = position_size
self.max_positions = max_positions
self.positions: List[Position] = []
self.historical_data: Optional[pd.DataFrame] = None
@abstractmethod
async def on_data(self, event: MarketDataEvent) -> Optional[SignalEvent]:
"""
处理市场数据更新
Args:
event: 市场数据事件
Returns:
如果产生交易信号,返回SignalEvent;否则返回None
"""
pass
@abstractmethod
async def calculate_signals(self, data: pd.DataFrame) -> List[SignalEvent]:
"""
基于历史数据计算交易信号
Args:
data: 历史市场数据
Returns:
交易信号列表
"""
pass
def update_position(self, position: Position, current_price: float) -> None:
"""
更新持仓的未实现盈亏
Args:
position: 需要更新的持仓
current_price: 当前市场价格
"""
if position.direction == "LONG":
position.unrealized_pnl = (current_price - position.entry_price) * position.size
else:
position.unrealized_pnl = (position.entry_price - current_price) * position.size
def can_open_position(self) -> bool:
"""检查是否可以开新仓位"""
return len(self.positions) < self.max_positions
def get_position_value(self) -> float:
"""获取当前持仓的总价值"""
return sum(abs(pos.unrealized_pnl) for pos in self.positions)
def get_total_pnl(self) -> float:
"""获取总盈亏(已实现 + 未实现)"""
unrealized = sum(pos.unrealized_pnl for pos in self.positions)
realized = sum(pos.realized_pnl for pos in self.positions)
return realized + unrealized
+65
View File
@@ -0,0 +1,65 @@
from typing import List, Optional
import pandas as pd
from datetime import datetime
from .base import Strategy, SignalEvent
from .base import Position
from ..data.base import MarketDataEvent
class MACrossoverStrategy(Strategy):
"""
简单移动平均交叉策略
快线上穿慢线做多,下穿做空
"""
def __init__(self, instrument: str, fast_period: int = 50, slow_period: int = 200, position_size: float = 1.0):
super().__init__(instrument, position_size)
self.fast_period = fast_period
self.slow_period = slow_period
async def on_data(self, event: MarketDataEvent) -> Optional[SignalEvent]:
if self.historical_data is None:
return None
# 更新收盘价
close = event.data.get('close') or event.data.get('mid')
self.historical_data.loc[event.timestamp] = {
'open': event.data.get('open', close),
'high': event.data.get('high', close),
'low': event.data.get('low', close),
'close': close
}
if len(self.historical_data) < self.slow_period:
return None
fast = self.historical_data['close'].rolling(self.fast_period).mean()
slow = self.historical_data['close'].rolling(self.slow_period).mean()
if fast.iloc[-2] <= slow.iloc[-2] and fast.iloc[-1] > slow.iloc[-1]:
return SignalEvent(
instrument=self.instrument,
timestamp=event.timestamp,
signal_type="LONG",
direction="BUY",
strength=self.position_size
)
if fast.iloc[-2] >= slow.iloc[-2] and fast.iloc[-1] < slow.iloc[-1]:
return SignalEvent(
instrument=self.instrument,
timestamp=event.timestamp,
signal_type="SHORT",
direction="SELL",
strength=self.position_size
)
return None
async def calculate_signals(self, data: pd.DataFrame) -> List[SignalEvent]:
signals = []
self.historical_data = data.copy()
fast = data['close'].rolling(self.fast_period).mean()
slow = data['close'].rolling(self.slow_period).mean()
for i in range(self.slow_period, len(data)):
ts = data.index[i]
if fast.iloc[i-1] <= slow.iloc[i-1] and fast.iloc[i] > slow.iloc[i]:
signals.append(SignalEvent(instrument=self.instrument, timestamp=ts, signal_type="LONG", direction="BUY", strength=self.position_size))
if fast.iloc[i-1] >= slow.iloc[i-1] and fast.iloc[i] < slow.iloc[i]:
signals.append(SignalEvent(instrument=self.instrument, timestamp=ts, signal_type="SHORT", direction="SELL", strength=self.position_size))
return signals
+46
View File
@@ -0,0 +1,46 @@
from typing import List, Optional
import pandas as pd
from datetime import datetime
from .base import Strategy, SignalEvent
from ..data.base import MarketDataEvent
class MomentumStrategy(Strategy):
"""
简单动量策略:当价格高于N日均线时做多,低于时做空
"""
def __init__(self, instrument: str, lookback: int = 20, position_size: float = 1.0):
super().__init__(instrument, position_size)
self.lookback = lookback
async def on_data(self, event: MarketDataEvent) -> Optional[SignalEvent]:
if self.historical_data is None:
return None
close = event.data.get('close') or event.data.get('mid')
self.historical_data.loc[event.timestamp] = {
'open': event.data.get('open', close),
'high': event.data.get('high', close),
'low': event.data.get('low', close),
'close': close
}
if len(self.historical_data) < self.lookback:
return None
ma = self.historical_data['close'].rolling(self.lookback).mean()
if close > ma.iloc[-1] and self.can_open_position():
return SignalEvent(self.instrument, event.timestamp, "LONG", "BUY", strength=self.position_size)
if close < ma.iloc[-1] and self.can_open_position():
return SignalEvent(self.instrument, event.timestamp, "SHORT", "SELL", strength=self.position_size)
return None
async def calculate_signals(self, data: pd.DataFrame) -> List[SignalEvent]:
signals = []
self.historical_data = data.copy()
ma = data['close'].rolling(self.lookback).mean()
for i in range(self.lookback, len(data)):
ts = data.index[i]
price = data['close'].iloc[i]
if price > ma.iloc[i-1]:
signals.append(SignalEvent(self.instrument, ts, "LONG", "BUY", strength=self.position_size))
elif price < ma.iloc[i-1]:
signals.append(SignalEvent(self.instrument, ts, "SHORT", "SELL", strength=self.position_size))
return signals
@@ -0,0 +1,150 @@
from typing import List, Optional
import pandas as pd
import numpy as np
from datetime import datetime
from ..data.base import MarketDataEvent
from .base import Strategy, SignalEvent
class RSIMeanReversionStrategy(Strategy):
"""
RSI均值回归策略
当RSI超买时做空,超卖时做多
"""
def __init__(
self,
instrument: str,
position_size: float = 1.0,
max_positions: int = 1,
rsi_period: int = 14,
overbought: float = 70.0,
oversold: float = 30.0,
stop_loss_atr: float = 2.0,
atr_period: int = 14
):
super().__init__(instrument, position_size, max_positions)
self.rsi_period = rsi_period
self.overbought = overbought
self.oversold = oversold
self.stop_loss_atr = stop_loss_atr
self.atr_period = atr_period
self.last_rsi = None
self.last_atr = None
@staticmethod
def calculate_rsi(data: pd.Series, period: int = 14) -> pd.Series:
"""计算RSI指标"""
delta = data.diff()
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
rs = gain / loss
return 100 - (100 / (1 + rs))
@staticmethod
def calculate_atr(data: pd.DataFrame, period: int = 14) -> pd.Series:
"""计算ATR指标"""
high = data['high']
low = data['low']
close = data['close']
tr1 = high - low
tr2 = abs(high - close.shift())
tr3 = abs(low - close.shift())
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
return tr.rolling(window=period).mean()
async def on_data(self, event: MarketDataEvent) -> Optional[SignalEvent]:
"""
处理实时市场数据
Args:
event: 市场数据事件
Returns:
如果触发信号则返回SignalEvent,否则返回None
"""
if self.historical_data is None:
return None
# 更新数据
current_price = event.data['mid']
self.historical_data.loc[event.timestamp] = current_price
# 计算指标
close_prices = self.historical_data['close']
rsi = self.calculate_rsi(close_prices, self.rsi_period).iloc[-1]
atr = self.calculate_atr(self.historical_data, self.atr_period).iloc[-1]
self.last_rsi = rsi
self.last_atr = atr
# 生成信号
if self.can_open_position():
if rsi > self.overbought:
return SignalEvent(
instrument=self.instrument,
timestamp=event.timestamp,
signal_type="SHORT",
direction="SELL",
strength=self.position_size,
stop_loss=current_price + self.stop_loss_atr * atr
)
elif rsi < self.oversold:
return SignalEvent(
instrument=self.instrument,
timestamp=event.timestamp,
signal_type="LONG",
direction="BUY",
strength=self.position_size,
stop_loss=current_price - self.stop_loss_atr * atr
)
return None
async def calculate_signals(self, data: pd.DataFrame) -> List[SignalEvent]:
"""
基于历史数据计算交易信号
Args:
data: 历史市场数据
Returns:
交易信号列表
"""
signals = []
self.historical_data = data.copy()
# 计算指标
close_prices = data['close']
rsi = self.calculate_rsi(close_prices, self.rsi_period)
atr = self.calculate_atr(data, self.atr_period)
# 生成信号
for i in range(self.rsi_period, len(data)):
timestamp = data.index[i]
current_price = close_prices[i]
current_rsi = rsi[i]
current_atr = atr[i]
if current_rsi > self.overbought:
signals.append(SignalEvent(
instrument=self.instrument,
timestamp=timestamp,
signal_type="SHORT",
direction="SELL",
strength=self.position_size,
stop_loss=current_price + self.stop_loss_atr * current_atr
))
elif current_rsi < self.oversold:
signals.append(SignalEvent(
instrument=self.instrument,
timestamp=timestamp,
signal_type="LONG",
direction="BUY",
strength=self.position_size,
stop_loss=current_price - self.stop_loss_atr * current_atr
))
return signals