use crate::backtesting::signal::Signal;
use crate::backtesting::strategy::{Strategy, StrategyContext};
use crate::indicators::Indicator;
use crate::models::chart::Candle;
#[derive(Clone)]
pub(super) struct EnterLongHold;
impl Strategy for EnterLongHold {
fn name(&self) -> &str {
"Enter Long Hold"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
if ctx.index == 0 && !ctx.has_position() {
Signal::long(ctx.timestamp(), ctx.close())
} else {
Signal::hold()
}
}
}
#[derive(Clone)]
pub(super) struct EnterShortHold;
impl Strategy for EnterShortHold {
fn name(&self) -> &str {
"Enter Short Hold"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
if ctx.index == 0 && !ctx.has_position() {
Signal::short(ctx.timestamp(), ctx.close())
} else {
Signal::hold()
}
}
}
#[derive(Clone)]
pub(super) struct EnterLongAt(pub usize);
impl Strategy for EnterLongAt {
fn name(&self) -> &str {
"Enter Long At"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
if ctx.index == self.0 && !ctx.has_position() {
Signal::long(ctx.timestamp(), ctx.close())
} else {
Signal::hold()
}
}
}
#[derive(Clone)]
pub(super) struct BuyStopAt(pub usize, pub f64);
impl Strategy for BuyStopAt {
fn name(&self) -> &str {
"Buy Stop At"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
if ctx.index == self.0 && !ctx.has_position() {
Signal::buy_stop(ctx.timestamp(), ctx.close(), self.1)
} else {
Signal::hold()
}
}
}
#[derive(Clone)]
pub(super) struct BuyLimitAt {
pub(super) bar: usize,
pub(super) limit_price: f64,
pub(super) expires_in_bars: Option<usize>,
}
impl Strategy for BuyLimitAt {
fn name(&self) -> &str {
"Buy Limit At"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
if ctx.index == self.bar && !ctx.has_position() {
let signal = Signal::buy_limit(ctx.timestamp(), ctx.close(), self.limit_price);
match self.expires_in_bars {
Some(n) => signal.expires_in_bars(n),
None => signal,
}
} else {
Signal::hold()
}
}
}
pub(super) fn make_candles(prices: &[f64]) -> Vec<Candle> {
prices
.iter()
.enumerate()
.map(|(i, &p)| Candle {
timestamp: i as i64,
open: p,
high: p * 1.01,
low: p * 0.99,
close: p,
volume: 1000,
adj_close: Some(p),
provider_id: None,
})
.collect()
}
pub(super) fn make_candles_with_timestamps(prices: &[f64], timestamps: &[i64]) -> Vec<Candle> {
prices
.iter()
.zip(timestamps.iter())
.map(|(&p, &ts)| Candle {
timestamp: ts,
open: p,
high: p * 1.01,
low: p * 0.99,
close: p,
volume: 1000,
adj_close: Some(p),
provider_id: None,
})
.collect()
}
pub(super) fn make_candle_ohlc(ts: i64, open: f64, high: f64, low: f64, close: f64) -> Candle {
Candle {
timestamp: ts,
open,
high,
low,
close,
volume: 1000,
adj_close: Some(close),
provider_id: None,
}
}
pub(super) fn make_candles_with_range(prices: &[f64], range_pct: f64) -> Vec<Candle> {
prices
.iter()
.enumerate()
.map(|(i, &p)| Candle {
timestamp: i as i64,
open: p,
high: p * (1.0 + range_pct),
low: p * (1.0 - range_pct),
close: p,
volume: 1000,
adj_close: Some(p),
provider_id: None,
})
.collect()
}
pub(super) fn make_alternating_candles(base: f64, swing_pct: f64, n: usize) -> Vec<Candle> {
let prices: Vec<f64> = (0..n)
.map(|i| {
if i.is_multiple_of(2) {
base
} else {
base * (1.0 + swing_pct)
}
})
.collect();
make_candles(&prices)
}
pub(super) struct EnterLongBar0;
impl Strategy for EnterLongBar0 {
fn name(&self) -> &str {
"Enter Long Bar 0"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
if ctx.index == 0 && !ctx.has_position() {
Signal::long(ctx.timestamp(), ctx.close())
} else {
Signal::hold()
}
}
}
#[derive(Clone)]
pub(super) struct EnterScaleInExit;
impl Strategy for EnterScaleInExit {
fn name(&self) -> &str {
"EnterScaleInExit"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
match ctx.index {
0 => Signal::long(ctx.timestamp(), ctx.close()),
1 if ctx.has_position() => Signal::scale_in(0.5, ctx.timestamp(), ctx.close()),
2 if ctx.has_position() => Signal::exit(ctx.timestamp(), ctx.close()),
_ => Signal::hold(),
}
}
}
#[derive(Clone)]
pub(super) struct EnterShortScaleIn;
impl Strategy for EnterShortScaleIn {
fn name(&self) -> &str {
"EnterShortScaleIn"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
match ctx.index {
0 => Signal::short(ctx.timestamp(), ctx.close()),
1 if ctx.has_position() => Signal::scale_in(0.5, ctx.timestamp(), ctx.close()),
_ => Signal::hold(),
}
}
}
#[derive(Clone)]
pub(super) struct EnterScaleOutExit;
impl Strategy for EnterScaleOutExit {
fn name(&self) -> &str {
"EnterScaleOutExit"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
match ctx.index {
0 => Signal::long(ctx.timestamp(), ctx.close()),
1 if ctx.has_position() => Signal::scale_out(0.5, ctx.timestamp(), ctx.close()),
2 if ctx.has_position() => Signal::exit(ctx.timestamp(), ctx.close()),
_ => Signal::hold(),
}
}
}
#[derive(Clone)]
pub(super) struct BracketLongStopLossStrategy {
pub(super) stop_pct: f64,
}
impl Strategy for BracketLongStopLossStrategy {
fn name(&self) -> &str {
"BracketLongStopLoss"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
if ctx.index == 0 && !ctx.has_position() {
Signal::long(ctx.timestamp(), ctx.close()).stop_loss(self.stop_pct)
} else {
Signal::hold()
}
}
}
#[derive(Clone)]
pub(super) struct BracketShortStopLossStrategy {
pub(super) stop_pct: f64,
}
impl Strategy for BracketShortStopLossStrategy {
fn name(&self) -> &str {
"BracketShortStopLoss"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
if ctx.index == 0 && !ctx.has_position() {
Signal::short(ctx.timestamp(), ctx.close()).stop_loss(self.stop_pct)
} else {
Signal::hold()
}
}
}
#[derive(Clone)]
pub(super) struct BracketLongTakeProfitStrategy {
pub(super) tp_pct: f64,
}
impl Strategy for BracketLongTakeProfitStrategy {
fn name(&self) -> &str {
"BracketLongTakeProfit"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
if ctx.index == 0 && !ctx.has_position() {
Signal::long(ctx.timestamp(), ctx.close()).take_profit(self.tp_pct)
} else {
Signal::hold()
}
}
}
#[derive(Clone)]
pub(super) struct BracketShortTakeProfitStrategy {
pub(super) tp_pct: f64,
}
impl Strategy for BracketShortTakeProfitStrategy {
fn name(&self) -> &str {
"BracketShortTakeProfit"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
if ctx.index == 0 && !ctx.has_position() {
Signal::short(ctx.timestamp(), ctx.close()).take_profit(self.tp_pct)
} else {
Signal::hold()
}
}
}
#[derive(Clone)]
pub(super) struct BracketLongTrailingStopStrategy {
pub(super) trail_pct: f64,
}
impl Strategy for BracketLongTrailingStopStrategy {
fn name(&self) -> &str {
"BracketLongTrailingStop"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
if ctx.index == 0 && !ctx.has_position() {
Signal::long(ctx.timestamp(), ctx.close()).trailing_stop(self.trail_pct)
} else {
Signal::hold()
}
}
}
#[derive(Clone)]
pub(super) struct BracketShortTrailingStopStrategy {
pub(super) trail_pct: f64,
}
impl Strategy for BracketShortTrailingStopStrategy {
fn name(&self) -> &str {
"BracketShortTrailingStop"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn on_candle(&self, ctx: &StrategyContext) -> Signal {
if ctx.index == 0 && !ctx.has_position() {
Signal::short(ctx.timestamp(), ctx.close()).trailing_stop(self.trail_pct)
} else {
Signal::hold()
}
}
}