Generated: 2025-01-10 | Commit: Current working tree
# Install dependencies
pip install -r requirements.txt
pip install -e ".[dev]" # With dev dependencies
# Run API server
python3 -m src.api.main
uvicorn src.api.main:app --host 0.0.0.0 --port 8000 --reload
# Lint (line-length: 100)
black src/ && flake8 src/ && mypy src/
# Test
pytest # All tests
pytest tests/test_informative.py # Single file
pytest tests/test_informative.py::TestClass # Single class
pytest tests/test_informative.py::TestClass::test_method # Single test
pytest --cov=src --cov-report=term-missing # With coveragecd frontend
pnpm install
pnpm dev # Port 3000
pnpm build # tsc && vite build
pnpm lint # ESLintdocker-compose up --build
docker-compose up -d && docker-compose logs -f
docker-compose down- Imports: Standard library → Third-party → Local, alphabetical within groups
- Line length: 100 characters (configured in
pyproject.toml) - Types: Required on all functions and variables
- Models: Use
dataclassfor data models (seesrc/core/types.py) - Enums: Extend
str, Enumfor string-based serialization - Config: Pydantic
BaseSettingswithenv_prefix(e.g.,IB__HOST) - Logging: Use
get_logger(__name__)fromsrc/utils/logger.py - Async: Use
asynccontextmanagerfor FastAPI lifespan - Error handling: NO bare
except:orexcept Exception. Catch specific exceptions. - Logging vs print: Use
loggereverywhere. Noprint()in production code.
- Imports: React → Third-party → Local, use
@/*alias for relative imports - Components: Functional components with hooks, TypeScript interfaces for props
- State: Zustand for global state,
useState/useReducerfor local state - Styling: Tailwind CSS, dark mode via
'class'strategy - Design: Typography-first, minimal icons (Stripe/Vercel/Linear style)
- TypeScript: Strict mode enabled, no
anyunless absolutely necessary
factor_mining/
├── src/ # Python backend (FastAPI)
│ ├── api/ # Routers (7 modules)
│ ├── config/ # Pydantic settings (env_prefix)
│ ├── core/ # Domain types (Signal, Order, PortfolioState)
│ ├── data/ # Collectors (IB, Polygon, CCXT)
│ ├── evaluation/ # Dual backtest engines + metrics
│ ├── execution/ # Broker implementations
│ ├── factors/ # 40+ technical factors
│ ├── strategies/ # Strategy implementations (v2)
│ └── utils/ # Logger
├── frontend/ # React/TypeScript + Vite
│ └── src/
│ ├── components/ # Charts, Layout
│ ├── pages/ # Dashboard, Backtest, History, Monitoring
│ ├── services/ # Axios API
│ └── stores/ # Zustand
├── examples/ # 13 demo scripts
├── tests/ # Unit tests (pytest + unittest)
└── data/ # Parquet cache, OHLCV
| Pattern | Location | Convention |
|---|---|---|
| Add strategy | src/strategies/ |
Import in __init__.py for auto-registration |
| Add factor | src/factors/technical/ |
Extend TechnicalFactor, register via FactorRegistry |
| API routes | src/api/routers/ |
Use /api/v1 prefix, factory pattern via create_app() |
| Settings | src/config/settings.py |
Nested Pydantic models, env_prefix per section |
| Core types | src/core/types.py |
Dataclass enums (Signal, Order, OrderStatus) |
- Tests at root:
test_*.pyin root instead oftests/. Fix: Put intests/. - Global variables:
_ib_broker,_db_store. Use dependency injection. - Silent failures: Bare
except:/except Exception. Catch specific types. - Print statements: Replace
print()withlogger.info()/logger.error(). - Type suppression: Never use
as any,@ts-ignore,@ts-expect-error. - Frontend visual changes: Delegate to
frontend-ui-ux-engineeragent.
- Strategy auto-registration: Import in
src/strategies/__init__.pytriggers registry - v2 migration: v1 API removed. Use
examples/usage_example.pyas v2 reference - Coverage configured but unused:
pytest-covinstalled, reports not generated - USE IB for data source: Default data source is Interactive Brokers
The system provides Freqtrade-style strategy templates that you can inherit and customize:
from src.strategies.base import RSIStrategy, TrendFollowingStrategy, sma, rsi
# 方式1: 继承策略模板
class MyRSIStrategy(RSIStrategy):
strategy_name = "My RSI Strategy"
rsi_period = 14
rsi_overbought = 70
rsi_oversold = 30
# 方式2: 使用策略工厂
from src.strategies.base import StrategyTemplateFactory
strategy = StrategyTemplateFactory.create(
'rsi',
strategy_id='my_rsi',
rsi_period=10
)| Template | Description | Key Parameters |
|---|---|---|
TrendFollowingStrategy |
Dual MA crossover | fast_period, slow_period, ma_type |
RSIStrategy |
RSI overbought/oversold | rsi_period, rsi_overbought, rsi_oversold |
MACDStrategy |
MACD crossover | macd_fast, macd_slow, macd_signal |
BollingerBandStrategy |
Bollinger band breakout | bb_period, bb_std |
StochasticStrategy |
Stochastic oscillator | k_period, d_period, overbought, oversold |
MomentumStrategy |
Momentum ranking | lookback_period, momentum_type |
MeanReversionStrategy |
Mean reversion | band_std, lookback |
MultiTimeframeStrategy |
Multi-timeframe analysis | entry_timeframe, exit_timeframe |
from src.strategies.base import sma, ema, rsi, macd, bollinger_bands, atr
# Moving averages
data['sma_20'] = sma(data['close'], 20)
data['ema_12'] = ema(data['close'], 12)
# Momentum
data['rsi_14'] = rsi(data['close'], 14)
macd_result = macd(data['close'], 12, 26, 9)
data['macd'] = macd_result.macd
data['macd_signal'] = macd_result.signal
# Volatility
bb = bollinger_bands(data['close'], 20, 2)
data['bb_upper'] = bb.upper
data['bb_lower'] = bb.lower
data['atr_14'] = atr(data['high'], data['low'], data['close'], 14)python3 examples/strategy_template_demo.pyThe system now has a unified event-driven architecture for backtesting and live trading:
src/data/providers/
├── base.py # DataFeed抽象接口
│ ├── DataFeed # 抽象基类
│ ├── HistoricalDataFeed # 历史回放(回测用)
│ └── DataFeedFactory # 数据源工厂
│
src/execution/providers/
├── base.py # ExecutionProvider抽象接口
│ ├── ExecutionProvider # 抽象基类
│ ├── SimulatedExecutionProvider # 模拟撮合(回测用)
│ └── ExecutionProviderFactory # 执行器工厂
│
src/evaluation/backtesting/
├── unified_backtest_engine.py # 统一回测引擎
│ └── UnifiedBacktestEngine # 事件驱动回测
MarketEvent → 策略.generate_signals() → SignalEvent
↓
SignalEvent → _check_risk() → RiskEvent
↓
RiskEvent → OrderCreatedEvent
↓
OrderCreatedEvent → ExecutionProvider.submit_order() → OrderFilledEvent
↓
OrderFilledEvent → PortfolioState.update() → 记录成交
| Issue | Before | After |
|---|---|---|
| Fixed price 100.0 | Hardcoded in execution_manager.py |
Real prices from DataFeed |
| Empty market data | current_prices = {} in event_backtest_engine.py |
Real data from bars_map |
| Empty risk check | pass in _check_risk() |
Full risk limits implementation |
| Two event engines | event_engine.py + event_backtest_engine.py |
Single UnifiedEventEngine |
| Non-deterministic | Async parallel processing in engine.py |
Controlled event flow |
from src.evaluation.backtesting.unified_backtest_engine import UnifiedBacktestEngine, BacktestConfig
from src.data.providers.base import HistoricalDataFeed, DataFeedFactory
from src.execution.providers.base import ExecutionProviderFactory
from src.strategies.base.unified_strategy import UnifiedStrategy
# 1. Create config
config = BacktestConfig(
initial_capital=100000,
commission_rate=0.001,
slippage_rate=0.0005,
fill_price_type="close",
max_position_size=0.1,
daily_loss_limit=0.05,
)
# 2. Create data feed and execution provider
data_feed = DataFeedFactory.create_historical_feed(warmup_days=260)
execution_provider = ExecutionProviderFactory.create_simulated_provider(
commission_rate=0.001,
slippage_rate=0.0005,
fill_price_type="close",
initial_capital=100000,
)
# 3. Create unified backtest engine
engine = UnifiedBacktestEngine(
config=config,
data_feed=data_feed,
execution_provider=execution_provider,
)
# 4. Run backtest
results = await engine.run(
strategies=[my_strategy],
universe=["AAPL", "MSFT", "GOOGL"],
start_date=date(2024, 1, 1),
end_date=date(2024, 12, 31),
)
# 5. Check results
print(f"Total Return: {results['results']['total_return_pct']:.2f}%")
print(f"Sharpe Ratio: {results['results']['sharpe_ratio']:.2f}")
print(f"Max Drawdown: {results['results']['max_drawdown_pct']:.2f}%")# Run unified backtest engine tests
pytest tests/test_unified_backtest_engine.py -v
# Run all tests
pytest tests/ -vThe system now supports Freqtrade-style strategies with a complete protocol system. This enables:
- Vectorized indicator calculation
- Freqtrade lifecycle callbacks
- Custom stoploss/ROI handling
- Trade confirmation callbacks
src/strategies/base/
├── freqtrade_interface.py # FreqtradeStrategy base class + Protocol
├── lifecycle.py # FreqtradeLifecycleMixin for v2 strategies
└── templates.py # Strategy templates (RSI, MACD, etc.)
src/evaluation/backtesting/
├── freqtrade_engine.py # FreqtradeBacktestEngine
├── freqtrade_config.py # FreqtradeBacktestConfig
├── freqtrade_report.py # FreqtradeReportGenerator
└── stoploss_manager.py # StoplossManager + ExitReason
src/execution/broker/
├── simulated_freqtrade.py # SimulatedFreqtradeBroker
└── simulated_freqtrade_protections.py # FreqtradeProtections
from src.strategies.base.freqtrade_interface import FreqtradeStrategy
from src.strategies.base.lifecycle import FreqtradeLifecycleMixin
import pandas as pd
class MyFreqtradeStrategy(FreqtradeStrategy, FreqtradeLifecycleMixin):
# Strategy Configuration
strategy_name = "My Strategy"
strategy_id = "my_strategy"
timeframe = "1d"
# Trading Configuration
stoploss = -0.10
trailing_stop = False
minimal_roi = {0: 0.02, "60": 0.01}
# Entry/Exit Configuration
use_exit_signal = True
exit_profit_only = False
def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
"""Calculate technical indicators."""
# Add your indicators here
return dataframe
def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
"""Generate entry signals."""
dataframe['enter_long'] = False # or your signal
dataframe['enter_tag'] = '' # optional signal tag
return dataframe
def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
"""Generate exit signals."""
dataframe['exit_long'] = False # or your signal
dataframe['exit_tag'] = '' # optional exit reason
return dataframe# Custom stoploss - return your stoploss rate
def custom_stoploss(self, pair, current_profit, current_rate, current_time, **kwargs) -> float:
return self.stoploss
# Custom sell logic - return reason string to trigger sell
def custom_sell(self, pair, current_profit, current_rate, current_time, **kwargs) -> Optional[str]:
return None # Use default logic
# Custom buy logic - return reason string
def custom_buy(self, pair, current_rate, current_time, **kwargs) -> Optional[str]:
return None
# Trade confirmation - return False to cancel order
def confirm_trade_entry(self, pair, order_type, amount, rate, time_in_force, current_time, **kwargs) -> bool:
return True
def confirm_trade_exit(self, pair, order_type, amount, rate, time_in_force, current_time, **kwargs) -> bool:
return True
# Post-fill handling
def order_filled(self, trade, order, current_time, **kwargs) -> None:
pass
# Lifecycle hooks
async def bot_start(self, **kwargs) -> None:
pass
async def bot_loop_start(self, **kwargs) -> None:
pass
async def botShutdown(self, **kwargs) -> None:
passIf you have an existing Strategy class (v2), add the mixin to support Freqtrade callbacks:
from src.strategies.base.strategy import Strategy
from src.strategies.base.lifecycle import FreqtradeLifecycleMixin
class MyStrategy(Strategy, FreqtradeLifecycleMixin):
# Your existing v2 code...
# Add Freqtrade callbacks as needed
def populate_indicators(self, dataframe, metadata) -> pd.DataFrame:
return dataframe
def populate_entry_trend(self, dataframe, metadata) -> pd.DataFrame:
dataframe['enter_long'] = dataframe['rsi'] < 30
return dataframefrom src.evaluation.backtesting.freqtrade_config import FreqtradeBacktestConfig
config = FreqtradeBacktestConfig(
timeframe="1d",
pairs=["SPY", "QQQ", "IWM"],
stake_amount=10000.0,
max_open_trades=3,
fee=0.001, # 0.1% commission
dry_run_wallet=100000.0, # Initial capital
enable_protections=True,
)from src.execution.broker.simulated_freqtrade import SimulatedFreqtradeBroker
from src.execution.broker.simulated_freqtrade_protections import FreqtradeProtections
protections = FreqtradeProtections(
max_drawdown_protection={"enabled": True, "max_drawdown": 0.15, "lookback_days": 30},
stoploss_on_exchange={"enabled": False},
max_open_trades={"enabled": True, "max_open_trades": 3},
cooldown_protection={"enabled": True, "cooldown_duration": 60},
blacklist_protection={"enabled": False},
)
broker = SimulatedFreqtradeBroker(
commission_rate=0.001,
slippage_rate=0.0005,
fill_price_type='close',
initial_capital=100000.0,
protections=protections,
)Available templates in StrategyTemplateFactory:
| Template | Description | Key Parameters |
|---|---|---|
TrendFollowingStrategy |
Dual MA crossover | fast_period, slow_period, ma_type |
RSIStrategy |
RSI overbought/oversold | rsi_period, rsi_overbought, rsi_oversold |
MACDStrategy |
MACD crossover | macd_fast, macd_slow, macd_signal |
BollingerBandStrategy |
Bollinger band breakout | bb_period, bb_std |
StochasticStrategy |
Stochastic oscillator | k_period, d_period, overbought, oversold |
MomentumStrategy |
Momentum ranking | lookback_period, momentum_type |
MeanReversionStrategy |
Mean reversion | band_std, lookback |
MultiTimeframeStrategy |
Multi-timeframe analysis | entry_timeframe, exit_timeframe |
Example with template:
from src.strategies.base import RSIStrategy
class MyRSIStrategy(RSIStrategy):
strategy_name = "My RSI Strategy"
rsi_period = 14
rsi_overbought = 70
rsi_oversold = 30The ETFMomentumJoinQuantStrategy demonstrates the full Freqtrade protocol:
from src.strategies.user_strategies.etf_momentum_joinquant import ETFMomentumJoinQuantStrategy
# Default configuration
strategy = ETFMomentumJoinQuantStrategy()
# Custom configuration
class CustomStrategy(ETFMomentumJoinQuantStrategy):
strategy_id = 'custom_etf'
etf_pool = ['QQQ', 'SPY', 'TLT', 'GLD']
lookback_days = 30
r2_threshold = 0.6
stoploss = -0.08
trailing_stop = TrueRun the example:
python3 examples/etf_momentum_joinquant_example.py| Aspect | v2 (Strategy) | v3 (FreqtradeStrategy) |
|---|---|---|
| Signal Generation | generate_signals() |
populate_entry_trend() / populate_exit_trend() |
| Indicators | Inline in signals | populate_indicators() first |
| Position Sizing | size_positions() |
Use stake_amount + max_position_size |
| Stoploss | stoploss_pct |
custom_stoploss() + stoploss attribute |
| ROI | take_profit_pct |
minimal_roi dict |
| Confirmation | Not available | confirm_trade_entry/exit() |
| Lifecycle | Limited | Full lifecycle (bot_start, botShutdown, etc.) |
# Core
from src.strategies.base.freqtrade_interface import FreqtradeStrategy
from src.strategies.base.lifecycle import FreqtradeLifecycleMixin
from src.strategies.base.templates import StrategyTemplateFactory
# Backtest
from src.evaluation.backtesting.freqtrade_engine import FreqtradeBacktestEngine
from src.evaluation.backtesting.freqtrade_config import FreqtradeBacktestConfig
from src.evaluation.backtesting.freqtrade_report import FreqtradeReportGenerator
from src.evaluation.backtesting.stoploss_manager import StoplossManager, ExitReason
# Broker
from src.execution.broker.simulated_freqtrade import SimulatedFreqtradeBroker
from src.execution.broker.simulated_freqtrade_protections import FreqtradeProtections
# Strategies
from src.strategies.user_strategies.etf_momentum_joinquant import ETFMomentumJoinQuantStrategy