File size: 4,198 Bytes
66dc1bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
from flask import Flask, request, jsonify
import yfinance as yf
import pandas as pd
import numpy as np
import talib
import datetime

# Bollinger Band Calculation
def calculate_bollinger(data, period=20, stddev=2):
    close = data['close']
    upper, middle, lower = talib.BBANDS(close, timeperiod=period, nbdevup=stddev, nbdevdn=stddev, matype=0)
    return upper, middle, lower


#BB Squeeze  breakout/fade after low volatility
def detect_bb_squeeze(close, upper, lower, middle, lookback=20, perc=20):
    bandwidth = (upper - lower) / middle
    # 20th percentile over the lookback window
    thresh = np.percentile(bandwidth.iloc[-lookback:], perc)
    if bandwidth.iloc[-1] < thresh:
        return "Neutral"
    # otherwise fall back to a breakout rule
    if close.iloc[-1] > upper.iloc[-1]:
        return "Bullish"
    elif close.iloc[-1] < lower.iloc[-1]:
        return "Bearish"
    return "Neutral"


# BB Breakout Detection
def detect_bb_breakout(close, upper, lower):
    if close.iloc[-1] > upper.iloc[-1]:
        return "Bullish"
    elif close.iloc[-1] < lower.iloc[-1]:
        return "Bearish"
    return "Neutral"


# BB Breakout Reversal
def detect_bb_breakout_reversal(data, upper, lower, middle, lookahead=3):
    
    i = len(data) - lookahead - 1  

    if i < 0:
        return "Neutral"

    row = data.iloc[i]
    # Bullish Reversal
    if row['close'] > upper.iloc[i]:
        for j in range(1, lookahead + 1):
            next_row = data.iloc[i + j]
            if next_row['close'] < upper.iloc[i + j] and next_row['close'] > middle.iloc[i + j]:
                return "Bullish"

    # Bearish Reversal
    elif row['close'] < lower.iloc[i]:
        for j in range(1, lookahead + 1):
            next_row = data.iloc[i + j]
            if next_row['close'] > lower.iloc[i + j] and next_row['close'] < middle.iloc[i + j]:
                return "Bearish"

    return "Neutral"



# Middle Band Pullback
def detect_middle_band_pullback(close, middle, upper, lower, threshold=0.10, trend_lookback=3):
    band_width = upper.iloc[-1] - lower.iloc[-1]
    if abs(close.iloc[-1] - middle.iloc[-1]) < band_width * threshold:
        trend_above = all(close.iloc[-i] > middle.iloc[-i] for i in range(2, 2 + trend_lookback))
        trend_below = all(close.iloc[-i] < middle.iloc[-i] for i in range(2, 2 + trend_lookback))
        if trend_above:
            return "Bullish"
        elif trend_below:
            return "Bearish"
    return "Neutral"



# Master strategy function
def bollinger_strategies(data):
    
    upper, middle, lower = calculate_bollinger(data)

    signals = {
        "UpperBand": round(upper.iloc[-1], 2),
        "MiddleBand": round(middle.iloc[-1], 2),
        "LowerBand": round(lower.iloc[-1], 2),
        "BB Squeeze": detect_bb_squeeze(data['close'], upper, lower, middle),
        "BB Breakout": detect_bb_breakout(data['close'], upper, lower),
        "BB Breakout Reversal": detect_bb_breakout_reversal(data, upper, lower, middle),
        "Middle Band Pullback": detect_middle_band_pullback(data['close'], middle, upper, lower)       
        
    }

    weights = {
        "BB Squeeze": 30,
        "BB Breakout": 25,
        "BB Breakout Reversal": 25,
        "Middle Band Pullback": 20        
    }

    total_score = 0
    for strategy, weight in weights.items():
        signal = signals[strategy]
        if "Bullish" in signal or "Breakout Up" in signal or "Squeeze" in signal or "Pullback" in signal:
            total_score += weight
        elif "Neutral" in signal or "No Breakout" in signal:
            total_score += weight * 0.5

    overall_percentage = round((total_score / sum(weights.values())) * 100, 2)

    if overall_percentage >= 60:
        final_signal = "Buy"
    elif overall_percentage <= 40:
        final_signal = "DBuy"
    else:
        final_signal = "Neutral"

    return signals, overall_percentage, final_signal

# API-style function
def get_bollinger_trade_signal(data):
    bb_signals, overall_score, final_signal = bollinger_strategies(data)
    return {
        "bollinger_signals": bb_signals,
        "bollinger_score": overall_score,
        "bollinger_final_signal": final_signal
    }