mirror of
https://github.com/xavierchuan/FX-ML-Trading-Engine.git
synced 2026-08-16 02:18:06 +00:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# core.strategy package init
|
||||
from . import base
|
||||
from . import rsi_mean_reversion
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user