Bitcoin favicon, more strategies, default timeframe 5m
- Add an SVG Bitcoin favicon and link it in index.html. - New indicators: macd() and roc(). - Five new strategies: macd_trend, bollinger_reversion, donchian_breakout, ema_cross, momentum (plus the existing sma_cross, rsi_reversion). - Default TIMEFRAME is now 5m (config, .env.example, compose, workflow, UI).
This commit is contained in:
1 parent
186a6949ee
commit
c500fb7572
18 files changed
+488
-11
No files matched your search
@@ -0,0 +1,99 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from app.engine.strategies.base import StrategyContext
|
||||
from app.schemas import Candle
|
||||
|
||||
|
||||
def make_candles(closes: Sequence[float]) -> list[Candle]:
|
||||
return [Candle(ts=i, open=c, high=c, low=c, close=c, volume=1.0) for i, c in enumerate(closes)]
|
||||
|
||||
|
||||
def context(closes: Sequence[float], position=None) -> StrategyContext:
|
||||
candles = make_candles(closes)
|
||||
return StrategyContext(candles=candles, position=position, equity=1000.0, cash=1000.0)
|
||||
|
||||
|
||||
def test_all_strategies_discovered() -> None:
|
||||
from app.engine.strategies import list_strategies
|
||||
|
||||
keys = {cls.key for cls in list_strategies()}
|
||||
assert {
|
||||
"sma_cross",
|
||||
"rsi_reversion",
|
||||
"macd_trend",
|
||||
"bollinger_reversion",
|
||||
"donchian_breakout",
|
||||
"ema_cross",
|
||||
"momentum",
|
||||
} <= keys
|
||||
|
||||
|
||||
def test_every_strategy_has_spec_and_params() -> None:
|
||||
from app.engine.strategies import list_strategies, strategy_spec
|
||||
|
||||
for cls in list_strategies():
|
||||
spec = strategy_spec(cls)
|
||||
assert spec.key and spec.name and spec.description
|
||||
# Defaults must round-trip through the constructor and be constructible.
|
||||
instance = cls({})
|
||||
assert instance.key == cls.key
|
||||
# A short synthetic series must never raise for a registered strategy.
|
||||
ctx = context([100, 101, 100, 102, 99, 103, 98, 104, 97, 105])
|
||||
instance.on_candle(ctx)
|
||||
|
||||
|
||||
def test_ema_cross_enters_on_cross_up() -> None:
|
||||
from app.engine.strategies import get_strategy
|
||||
|
||||
strategy = get_strategy("ema_cross")({"fast": 2, "slow": 4, "trend_filter": 0, "size": 0.5})
|
||||
# Fast EMA dips below the slow EMA, then crosses back above.
|
||||
ctx = context([10, 10, 10, 10, 10, 10, 10, 10, 7, 9, 12])
|
||||
strategy.on_candle(ctx)
|
||||
intents = ctx.take_intents()
|
||||
assert intents and intents[0].action == "enter_long"
|
||||
|
||||
|
||||
def test_momentum_enters_on_positive_roc() -> None:
|
||||
from app.engine.strategies import get_strategy
|
||||
|
||||
strategy = get_strategy("momentum")({"period": 3, "entry_threshold": 0.01, "atr_stop_mult": 0})
|
||||
ctx = context([100, 100, 100, 105])
|
||||
strategy.on_candle(ctx)
|
||||
intents = ctx.take_intents()
|
||||
assert intents and intents[0].action == "enter_long"
|
||||
|
||||
|
||||
def test_macd_trend_no_intent_when_flat_short_history() -> None:
|
||||
from app.engine.strategies import get_strategy
|
||||
|
||||
strategy = get_strategy("macd_trend")({})
|
||||
ctx = context([100, 101, 102])
|
||||
strategy.on_candle(ctx)
|
||||
assert ctx.take_intents() == []
|
||||
|
||||
|
||||
def test_bollinger_enters_below_lower_band() -> None:
|
||||
from app.engine.strategies import get_strategy
|
||||
|
||||
strategy = get_strategy("bollinger_reversion")(
|
||||
{"period": 5, "num_std": 1.0, "atr_stop_mult": 0}
|
||||
)
|
||||
# Flat then a sharp drop below the lower band.
|
||||
ctx = context([100, 100, 100, 100, 80])
|
||||
strategy.on_candle(ctx)
|
||||
intents = ctx.take_intents()
|
||||
assert intents and intents[0].action == "enter_long"
|
||||
|
||||
|
||||
def test_donchian_enters_on_breakout() -> None:
|
||||
from app.engine.strategies import get_strategy
|
||||
|
||||
strategy = get_strategy("donchian_breakout")(
|
||||
{"entry_period": 5, "exit_period": 3, "atr_stop_mult": 0}
|
||||
)
|
||||
ctx = context([100, 100, 100, 100, 100, 110])
|
||||
strategy.on_candle(ctx)
|
||||
intents = ctx.take_intents()
|
||||
assert intents and intents[0].action == "enter_long"
|
||||
Reference in new issue
Block a user