The Fractal Trail [UAlgo] is designed to identify and utilize Williams fractals as dynamic trailing stops. This tool serves traders by marking key fractal points on the chart and leveraging them to create adaptive stop-loss trails, enhancing risk management and trade decision-making.
Williams fractals are pivotal in identifying potential reversals and critical support/resistance levels. By plotting fractals dynamically and providing configurable options, this indicator allows for personalized adjustments based on the trader’s strategy.
This script integrates both visual fractal markers and adjustable trailing stops, offering insights into market trends while catering to a wide variety of trading styles and timeframes.
``` // This Pine Script™ code is subject to the terms of the Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
// © UAlgo
//@version=5
indicator(title='Fractal Trail [UAlgo]', shorttitle='Fractal Trail [UAlgo]', overlay=true)
bool showWilliamsFractals = input.bool(defval=true, title='Show All Williams Fractals', group="Fractal Trail [UAlgo]", tooltip='Places triangle indicators to mark each Williams fractal position')
bool showWilliamsStops = input.bool(defval=true, title='Show Williams Trail', tooltip='Creates a dynamic trailing stop based on the latest identified fractal', group="Fractal Trail [UAlgo]")
int williamsLeftRange = input.int(defval=2, minval=1, maxval=50, title='Williams Fractal Range Left/Right', group="Fractal Trail [UAlgo]", tooltip='Specifies the number of previous bars to analyze for the range. Standard setting is two bars in both directions.',inline = "range")
int williamsRightRange = input.int(defval=2, minval=1, maxval=50, title=' ', tooltip='Determines the number of forward bars to examine for the range. Best practice suggests matching the backward range value.', group="Fractal Trail [UAlgo]",inline = "range")
float williamsStopBuffer = input.float(defval=0, minval=0, maxval=20, step=0.5, title='Trail Buffer Percent (%)', group="Fractal Trail [UAlgo]", tooltip='Sets a percentage-based safety margin between the fractal price and the trailing stop')
string flipInput = input.string(title='Trail Invalidation Source', defval='Close', options=['Close', 'Wick'], tooltip='Determines whether the trail flips based on candle closing prices or extreme points (high/low)', group="Fractal Trail [UAlgo]")
color upTrailColor = input.color(defval=color.rgb(40, 218, 150), title='Up/Down Trail Color', group="Fractal Trail [UAlgo]",inline = "trailC")
color dnTrailColor = input.color(defval=color.new(color.blue, 0), title=' ', group="Fractal Trail [UAlgo]",inline = "trailC")
color fillColorUp = color.new(color.rgb(40, 218, 150), 80)
color fillColorDn = color.new(color.blue, 80)
f_IsWilliamsFractal(_leftRange, _rightRange, _type) =>
_isFractal = _type == 'high' and high[_rightRange] >= ta.highest(high, _leftRange + _rightRange + 1) or _type == 'low' and low[_rightRange] <= ta.lowest(low, _leftRange + _rightRange + 1)
_fractalValue = _isFractal and _type == 'high' ? high[_rightRange] : _isFractal and _type == 'low' ? low[_rightRange] : na
[_isFractal, _fractalValue]
[isHighFractal, highFractalPrice] = f_IsWilliamsFractal(williamsLeftRange, williamsRightRange, 'high')
[isLowFractal, lowFractalPrice] = f_IsWilliamsFractal(williamsLeftRange, williamsRightRange, 'low')
isHighFractal := isHighFractal[1] ? false : isHighFractal
isLowFractal := isLowFractal[1] ? false : isLowFractal
highFractalOffset = 0 - williamsRightRange
color white10 = color.new(color.white, 10)
plotshape(isHighFractal and showWilliamsFractals, title='Shape for Williams High', style=shape.triangledown, location=location.abovebar, color=white10, size=size.tiny, offset=highFractalOffset)
lowFractalOffset = 0 - williamsRightRange
plotshape(isLowFractal and showWilliamsFractals, title='Shape for Williams Low', style=shape.triangleup, location=location.belowbar, color=white10, size=size.tiny, offset=lowFractalOffset)
f_addPercentBuffer(_input, _buffer, _direction) =>
_direction == 'plus' ? _input * (1 + _buffer / 100) : _direction == 'minus' ? _input * (1 - _buffer / 100) : na
lowFractalPriceBuffered = f_addPercentBuffer(lowFractalPrice, williamsStopBuffer, 'minus')
highFractalPriceBuffered = f_addPercentBuffer(highFractalPrice, williamsStopBuffer, 'plus')
f_persistAndReset(_trigger, _source) =>
var float _output = 0.0
_output := _trigger ? _source : _output[1]
_output
longStopPrice = f_persistAndReset(isLowFractal, lowFractalPriceBuffered)
shortStopPrice = f_persistAndReset(isHighFractal, highFractalPriceBuffered)
f_trail(_source, _trail, _direction) =>
_direction == 'down' and _source >= _trail[1] ? _trail : _direction == 'up' and _source <= _trail[1] ? _trail : _source
var float longStopPriceTrail = longStopPrice
var float shortStopPriceTrail = shortStopPrice
shortStopPriceTrail := f_trail(shortStopPrice, shortStopPriceTrail, 'down')
longStopPriceTrail := f_trail(longStopPrice, longStopPriceTrail, 'up')
f_flip(_flipInput, _longTrail, _shortTrail, _longReset, _shortReset) =>
var bool _flipLongNow = false
var bool _flipShortNow = false
var bool _isLong = true
var bool _isShort = true
float _flipLongSource = _flipInput == 'Close' ? close : _flipInput == 'Wick' ? high : na
float _flipShortSource = _flipInput == 'Close' ? close : _flipInput == 'Wick' ? low : na
_flipLongNow := _isShort[1] and _flipLongSource > _shortTrail ? true : false
_flipShortNow := _isLong[1] and _flipShortSource < _longTrail ? true : false
_flipLongNow := _flipShortNow and _flipLongNow and close > _longTrail ? true : _flipShortNow and _flipLongNow and close <= _longTrail ? false : _flipLongNow
_flipShortNow := _flipLongNow and _flipShortNow and close < _shortTrail ? true : _flipShortNow and _flipLongNow and close >= _shortTrail ? false : _flipShortNow
_isLong := _flipLongNow ? true : _flipShortNow ? false : na(_isLong[1]) ? true : _isLong[1]
_isShort := _flipShortNow ? true : _flipLongNow ? false : na(_isShort[1]) ? true : _isShort[1]
_longTrailOutput = _longTrail
_shortTrailOutput = _shortTrail
_longTrailOutput := _isLong and not _isLong[1] ? _longReset : _longTrailOutput
_shortTrailOutput := _isShort and not _isShort[1] ? _shortReset : _shortTrailOutput
float _longTrailPlot = _isLong ? _longTrailOutput : _isLong and _isShort ? _longTrailOutput : na
float _shortTrailPlot = _isShort ? _shortTrailOutput : _isLong and _isShort ? _shortTrailOutput : na
[_longTrailOutput, _shortTrailOutput, _longTrailPlot, _shortTrailPlot]
f_getFlipResetWilliamsLong(_longTrail, _buffer, _isLowFractal, _rightRange) =>
_barIndexWhenLastFractalConfirmed = ta.valuewhen(_isLowFractal, bar_index, 0)
_barsSinceLastFractalConfirmed = bar_index - _barIndexWhenLastFractalConfirmed
int _barsToGoBack = _barsSinceLastFractalConfirmed + _rightRange
float _lowestLow = low
for i = 0 to _barsToGoBack by 1
_lowestLow := math.min(low[i], _lowestLow)
_lowestLow
_lowestLowAdjusted = _lowestLow * (1 - _buffer / 100)
_lowestLowAdjusted
f_getFlipResetWilliamsShort(_shortTrail, _buffer, _isHighFractal, _leftRange) =>
_barIndexWhenLastFractalConfirmed = ta.valuewhen(_isHighFractal, bar_index, 0)
_barsSinceLastFractalConfirmed = bar_index - _barIndexWhenLastFractalConfirmed
int _barsToGoBack = _barsSinceLastFractalConfirmed + _leftRange
float _highestHigh = high
for i = 0 to _barsToGoBack by 1
_highestHigh := math.max(high[i], _highestHigh)
_highestHigh
_highestHighAdjusted = _highestHigh * (1 + _buffer / 100)
_highestHighAdjusted
longStopPrice := f_getFlipResetWilliamsLong(longStopPrice, williamsStopBuffer, isLowFractal, williamsRightRange)
shortStopPrice := f_getFlipResetWilliamsShort(shortStopPrice, williamsStopBuffer, isHighFractal, williamsLeftRange)
[longStopPriceTrailTemp, shortStopPriceTrailTemp, longStopPriceTrailPlot, shortStopPriceTrailPlot] = f_flip(flipInput, longStopPriceTrail, shortStopPriceTrail, longStopPrice, shortStopPrice)
longStopPriceTrail := longStopPriceTrailTemp
shortStopPriceTrail := shortStopPriceTrailTemp
shortStopPriceTrailPlotDisplay = showWilliamsStops ? shortStopPriceTrailPlot : na
longStopPriceTrailPlotDisplay = showWilliamsStops ? longStopPriceTrailPlot : na
plot(shortStopPriceTrailPlotDisplay, 'Williams Trailing Stop High Price', color=dnTrailColor, style=plot.style_linebr, linewidth=3)
plot(shortStopPriceTrailPlotDisplay, 'Williams Trailing Stop High Price Highlight', color=dnTrailColor, style=plot.style_linebr, linewidth=1)
plot(longStopPriceTrailPlotDisplay, 'Williams Trailing Stop Low Price', color=upTrailColor, style=plot.style_linebr, linewidth=3)
plot(longStopPriceTrailPlotDisplay, 'Williams Trailing Stop Low Price Highlight', color=upTrailColor, style=plot.style_linebr, linewidth=1)
pricefill = plot(hl2, 'Midline', color=color.gray, display=display.none)
isUptrend = close < shortStopPriceTrailPlotDisplay
isDowntrend = close > longStopPriceTrailPlotDisplay
fillColor = isUptrend ? color.new(fillColorDn, 85) : color.new(fillColorUp, 85)
fillPrice = isUptrend ? shortStopPriceTrailPlotDisplay : longStopPriceTrailPlotDisplay
fill(plot(fillPrice, title='Williams Trailing Stop Fill High'), pricefill, color=fillColor)
alertCrossLongStop = na(longStopPriceTrailPlot) and not na(longStopPriceTrailPlot[1])
alertCrossShortStop = na(shortStopPriceTrailPlot) and not na(shortStopPriceTrailPlot[1])
alertcondition(alertCrossLongStop, title='Crossed Williams Long Stop', message='Alert from Williams Fractal Trailing Stops: \n {{ticker}} price crossed long stop')
alertcondition(alertCrossShortStop, title='Crossed Williams Short Stop', message='Alert from Williams Fractal Trailing Stops: \n {{ticker}} price crossed short stop')
alertcondition(isHighFractal, title='High Printed', message='Alert from Williams Fractal Trailing Stops: \n {{ticker}} Williams High has been confirmed')
alertcondition(isLowFractal, title='Low Printed', message='Alert from Williams Fractal Trailing Stops: \n {{ticker}} Williams Low has been confirmed') ``` study this code
// This Pine Script™ code is subject to the terms of the Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
// © UAlgo
//@version=5
indicator(title='Fractal Trail [UAlgo]', shorttitle='Fractal Trail [UAlgo]', overlay=true)
bool showWilliamsFractals = input.bool(defval=true, title='Show All Williams Fractals', group="Fractal Trail [UAlgo]", tooltip='Places triangle indicators to mark each Williams fractal position')
bool showWilliamsStops = input.bool(defval=true, title='Show Williams Trail', tooltip='Creates a dynamic trailing stop based on the latest identified fractal', group="Fractal Trail [UAlgo]")
int williamsLeftRange = input.int(defval=2, minval=1, maxval=50, title='Williams Fractal Range Left/Right', group="Fractal Trail [UAlgo]", tooltip='Specifies the number of previous bars to analyze for the range. Standard setting is two bars in both directions.',inline = "range")
int williamsRightRange = input.int(defval=2, minval=1, maxval=50, title=' ', tooltip='Determines the number of forward bars to examine for the range. Best practice suggests matching the backward range value.', group="Fractal Trail [UAlgo]",inline = "range")
float williamsStopBuffer = input.float(defval=0, minval=0, maxval=20, step=0.5, title='Trail Buffer Percent (%)', group="Fractal Trail [UAlgo]", tooltip='Sets a percentage-based safety margin between the fractal price and the trailing stop')
string flipInput = input.string(title='Trail Invalidation Source', defval='Close', options=['Close', 'Wick'], tooltip='Determines whether the trail flips based on candle closing prices or extreme points (high/low)', group="Fractal Trail [UAlgo]")
color upTrailColor = input.color(defval=color.rgb(40, 218, 150), title='Up/Down Trail Color', group="Fractal Trail [UAlgo]",inline = "trailC")
color dnTrailColor = input.color(defval=color.new(color.blue, 0), title=' ', group="Fractal Trail [UAlgo]",inline = "trailC")
color fillColorUp = color.new(color.rgb(40, 218, 150), 80)
color fillColorDn = color.new(color.blue, 80)
f_IsWilliamsFractal(_leftRange, _rightRange, _type) =>
_isFractal = _type == 'high' and high[_rightRange] >= ta.highest(high, _leftRange + _rightRange + 1) or _type == 'low' and low[_rightRange] <= ta.lowest(low, _leftRange + _rightRange + 1)
_fractalValue = _isFractal and _type == 'high' ? high[_rightRange] : _isFractal and _type == 'low' ? low[_rightRange] : na
[_isFractal, _fractalValue]
[isHighFractal, highFractalPrice] = f_IsWilliamsFractal(williamsLeftRange, williamsRightRange, 'high')
[isLowFractal, lowFractalPrice] = f_IsWilliamsFractal(williamsLeftRange, williamsRightRange, 'low')
isHighFractal := isHighFractal[1] ? false : isHighFractal
isLowFractal := isLowFractal[1] ? false : isLowFractal
highFractalOffset = 0 - williamsRightRange
color white10 = color.new(color.white, 10)
plotshape(isHighFractal and showWilliamsFractals, title='Shape for Williams High', style=shape.triangledown, location=location.abovebar, color=white10, size=size.tiny, offset=highFractalOffset)
lowFractalOffset = 0 - williamsRightRange
plotshape(isLowFractal and showWilliamsFractals, title='Shape for Williams Low', style=shape.triangleup, location=location.belowbar, color=white10, size=size.tiny, offset=lowFractalOffset)
f_addPercentBuffer(_input, _buffer, _direction) =>
_direction == 'plus' ? _input * (1 + _buffer / 100) : _direction == 'minus' ? _input * (1 - _buffer / 100) : na
lowFractalPriceBuffered = f_addPercentBuffer(lowFractalPrice, williamsStopBuffer, 'minus')
highFractalPriceBuffered = f_addPercentBuffer(highFractalPrice, williamsStopBuffer, 'plus')
f_persistAndReset(_trigger, _source) =>
var float _output = 0.0
_output := _trigger ? _source : _output[1]
_output
longStopPrice = f_persistAndReset(isLowFractal, lowFractalPriceBuffered)
shortStopPrice = f_persistAndReset(isHighFractal, highFractalPriceBuffered)
f_trail(_source, _trail, _direction) =>
_direction == 'down' and _source >= _trail[1] ? _trail : _direction == 'up' and _source <= _trail[1] ? _trail : _source
var float longStopPriceTrail = longStopPrice
var float shortStopPriceTrail = shortStopPrice
shortStopPriceTrail := f_trail(shortStopPrice, shortStopPriceTrail, 'down')
longStopPriceTrail := f_trail(longStopPrice, longStopPriceTrail, 'up')
f_flip(_flipInput, _longTrail, _shortTrail, _longReset, _shortReset) =>
var bool _flipLongNow = false
var bool _flipShortNow = false
var bool _isLong = true
var bool _isShort = true
float _flipLongSource = _flipInput == 'Close' ? close : _flipInput == 'Wick' ? high : na
float _flipShortSource = _flipInput == 'Close' ? close : _flipInput == 'Wick' ? low : na
_flipLongNow := _isShort[1] and _flipLongSource > _shortTrail ? true : false
_flipShortNow := _isLong[1] and _flipShortSource < _longTrail ? true : false
_flipLongNow := _flipShortNow and _flipLongNow and close > _longTrail ? true : _flipShortNow and _flipLongNow and close <= _longTrail ? false : _flipLongNow
_flipShortNow := _flipLongNow and _flipShortNow and close < _shortTrail ? true : _flipShortNow and _flipLongNow and close >= _shortTrail ? false : _flipShortNow
_isLong := _flipLongNow ? true : _flipShortNow ? false : na(_isLong[1]) ? true : _isLong[1]
_isShort := _flipShortNow ? true : _flipLongNow ? false : na(_isShort[1]) ? true : _isShort[1]
_longTrailOutput = _longTrail
_shortTrailOutput = _shortTrail
_longTrailOutput := _isLong and not _isLong[1] ? _longReset : _longTrailOutput
_shortTrailOutput := _isShort and not _isShort[1] ? _shortReset : _shortTrailOutput
float _longTrailPlot = _isLong ? _longTrailOutput : _isLong and _isShort ? _longTrailOutput : na
float _shortTrailPlot = _isShort ? _shortTrailOutput : _isLong and _isShort ? _shortTrailOutput : na
[_longTrailOutput, _shortTrailOutput, _longTrailPlot, _shortTrailPlot]
f_getFlipResetWilliamsLong(_longTrail, _buffer, _isLowFractal, _rightRange) =>
_barIndexWhenLastFractalConfirmed = ta.valuewhen(_isLowFractal, bar_index, 0)
_barsSinceLastFractalConfirmed = bar_index - _barIndexWhenLastFractalConfirmed
int _barsToGoBack = _barsSinceLastFractalConfirmed + _rightRange
float _lowestLow = low
for i = 0 to _barsToGoBack by 1
_lowestLow := math.min(low[i], _lowestLow)
_lowestLow
_lowestLowAdjusted = _lowestLow * (1 - _buffer / 100)
_lowestLowAdjusted
f_getFlipResetWilliamsShort(_shortTrail, _buffer, _isHighFractal, _leftRange) =>
_barIndexWhenLastFractalConfirmed = ta.valuewhen(_isHighFractal, bar_index, 0)
_barsSinceLastFractalConfirmed = bar_index - _barIndexWhenLastFractalConfirmed
int _barsToGoBack = _barsSinceLastFractalConfirmed + _leftRange
float _highestHigh = high
for i = 0 to _barsToGoBack by 1
_highestHigh := math.max(high[i], _highestHigh)
_highestHigh
_highestHighAdjusted = _highestHigh * (1 + _buffer / 100)
_highestHighAdjusted
longStopPrice := f_getFlipResetWilliamsLong(longStopPrice, williamsStopBuffer, isLowFractal, williamsRightRange)
shortStopPrice := f_getFlipResetWilliamsShort(shortStopPrice, williamsStopBuffer, isHighFractal, williamsLeftRange)
[longStopPriceTrailTemp, shortStopPriceTrailTemp, longStopPriceTrailPlot, shortStopPriceTrailPlot] = f_flip(flipInput, longStopPriceTrail, shortStopPriceTrail, longStopPrice, shortStopPrice)
longStopPriceTrail := longStopPriceTrailTemp
shortStopPriceTrail := shortStopPriceTrailTemp
shortStopPriceTrailPlotDisplay = showWilliamsStops ? shortStopPriceTrailPlot : na
longStopPriceTrailPlotDisplay = showWilliamsStops ? longStopPriceTrailPlot : na
plot(shortStopPriceTrailPlotDisplay, 'Williams Trailing Stop High Price', color=dnTrailColor, style=plot.style_linebr, linewidth=3)
plot(shortStopPriceTrailPlotDisplay, 'Williams Trailing Stop High Price Highlight', color=dnTrailColor, style=plot.style_linebr, linewidth=1)
plot(longStopPriceTrailPlotDisplay, 'Williams Trailing Stop Low Price', color=upTrailColor, style=plot.style_linebr, linewidth=3)
plot(longStopPriceTrailPlotDisplay, 'Williams Trailing Stop Low Price Highlight', color=upTrailColor, style=plot.style_linebr, linewidth=1)
pricefill = plot(hl2, 'Midline', color=color.gray, display=display.none)
isUptrend = close < shortStopPriceTrailPlotDisplay
isDowntrend = close > longStopPriceTrailPlotDisplay
fillColor = isUptrend ? color.new(fillColorDn, 85) : color.new(fillColorUp, 85)
fillPrice = isUptrend ? shortStopPriceTrailPlotDisplay : longStopPriceTrailPlotDisplay
fill(plot(fillPrice, title='Williams Trailing Stop Fill High'), pricefill, color=fillColor)
alertCrossLongStop = na(longStopPriceTrailPlot) and not na(longStopPriceTrailPlot[1])
alertCrossShortStop = na(shortStopPriceTrailPlot) and not na(shortStopPriceTrailPlot[1])
alertcondition(alertCrossLongStop, title='Crossed Williams Long Stop', message='Alert from Williams Fractal Trailing Stops: \n {{ticker}} price crossed long stop')
alertcondition(alertCrossShortStop, title='Crossed Williams Short Stop', message='Alert from Williams Fractal Trailing Stops: \n {{ticker}} price crossed short stop')
alertcondition(isHighFractal, title='High Printed', message='Alert from Williams Fractal Trailing Stops: \n {{ticker}} Williams High has been confirmed')
alertcondition(isLowFractal, title='Low Printed', message='Alert from Williams Fractal Trailing Stops: \n {{ticker}} Williams Low has been confirmed')
import pandas as pd
import numpy as np
def find_fractals(df, left_range=2, right_range=2):
high_fractals = []
low_fractals = []
for i in range(left_range, len(df) - right_range):
is_high_fractal = all(df['High'][i] > df['High'][i - j] for j in range(1, left_range + 1)) and all(df['High'][i] > df['High'][i + j] for j in range(1, right_range + 1))
is_low_fractal = all(df['Low'][i] < df['Low'][i - j] for j in range(1, left_range + 1)) and all(df['Low'][i] < df['Low'][i + j] for j in range(1, right_range + 1))
high_fractals.append(df['High'][i] if is_high_fractal else np.nan)
low_fractals.append(df['Low'][i] if is_low_fractal else np.nan)
return pd.Series(high_fractals, index=df.index[left_range:len(df) - right_range]), pd.Series(low_fractals, index=df.index[left_range:len(df) - right_range])
def add_buffer(fractal_prices, buffer_percent, direction='minus'):
if direction == 'plus':
return fractal_prices * (1 + buffer_percent / 100)
elif direction == 'minus':
return fractal_prices * (1 - buffer_percent / 100)
else:
return fractal_prices
def update_trailing_stop(fractal_prices, previous_trail, price_data, direction='up'):
trail = []
for i in range(len(fractal_prices)):
if direction == 'down' and price_data[i] >= previous_trail:
trail.append(previous_trail)
elif direction == 'up' and price_data[i] <= previous_trail:
trail.append(previous_trail)
else:
trail.append(fractal_prices[i])
previous_trail = trail[-1]
return pd.Series(trail, index=fractal_prices.index)
def flip_trend(price_data, long_trail, short_trail):
is_long = True
long_trail_output = []
short_trail_output = []
for i in range(len(price_data)):
if is_long and price_data[i] < short_trail[i]:
is_long = False
elif not is_long and price_data[i] > long_trail[i]:
is_long = True
long_trail_output.append(long_trail[i] if is_long else np.nan)
short_trail_output.append(short_trail[i] if not is_long else np.nan)
return pd.Series(long_trail_output, index=price_data.index), pd.Series(short_trail_output, index=price_data.index)
import matplotlib.pyplot as plt
def plot_trailing_stops(df, long_trail, short_trail):
plt.figure(figsize=(14, 7))
plt.plot(df.index, df['Close'], label='Price', color='black')
plt.plot(long_trail.index, long_trail, label='Long Stop Price', linestyle='--', color='green')
plt.plot(short_trail.index, short_trail, label='Short Stop Price', linestyle='--', color='red')
plt.legend()
plt.show()
# Assuming 'df' is your DataFrame containing the price data.
plot_trailing_stops(df, long_stop_price_trail, short_stop_price_trail)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf
from datetime import datetime, timedelta
# Function to load data from Yahoo Finance
def load_stock_data(ticker, start_date, end_date):
stock = yf.download(ticker, start=start_date, end=end_date)
stock.reset_index(inplace=True)
return stock
# Function to format the ticker for Yahoo Finance
def format_ticker(ticker):
if ticker.isdigit(): # Korean stock ticker
return ticker + ".KS"
return ticker # US or other tickers
# Load actual financial data
end_date = datetime.now()
start_date = end_date - timedelta(days=200)
ticker = 'AAPL' # Example ticker, replace with desired ticker
ticker = format_ticker(ticker)
df = load_stock_data(ticker, start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d'))
# 1. Find Fractals
def find_fractals(df, left_range=2, right_range=2):
high_fractals = []
low_fractals = []
for i in range(left_range, len(df) - right_range):
is_high_fractal = all(df['High'][i] > df['High'][i - j] for j in range(1, left_range + 1)) and all(df['High'][i] > df['High'][i + j] for j in range(1, right_range + 1))
is_low_fractal = all(df['Low'][i] < df['Low'][i - j] for j in range(1, left_range + 1)) and all(df['Low'][i] < df['Low'][i + j] for j in range(1, right_range + 1))
high_fractals.append(df['High'][i] if is_high_fractal else np.nan)
low_fractals.append(df['Low'][i] if is_low_fractal else np.nan)
high_fractals = [np.nan] * left_range + high_fractals + [np.nan] * right_range
low_fractals = [np.nan] * left_range + low_fractals + [np.nan] * right_range
return pd.Series(high_fractals, index=df.index), pd.Series(low_fractals, index=df.index)
df['High_Fractal'], df['Low_Fractal'] = find_fractals(df)
# 2. Add Buffer to Fractals
def add_buffer(fractal_prices, buffer_percent, direction='minus'):
if direction == 'plus':
return fractal_prices * (1 + buffer_percent / 100)
elif direction == 'minus':
return fractal_prices * (1 - buffer_percent / 100)
else:
return fractal_prices
df['High_Fractal_Buffered'] = add_buffer(df['High_Fractal'], buffer_percent=2, direction='plus')
df['Low_Fractal_Buffered'] = add_buffer(df['Low_Fractal'], buffer_percent=2, direction='minus')
# 3. Update Trailing Stop
def update_trailing_stop(fractal_prices, previous_trail, price_data, direction='up'):
trail = []
for i in range(len(fractal_prices)):
if i == 0:
trail.append(fractal_prices[i] if not np.isnan(fractal_prices[i]) else price_data[i])
else:
if direction == 'down' and price_data[i] >= previous_trail:
trail.append(previous_trail)
elif direction == 'up' and price_data[i] <= previous_trail:
trail.append(previous_trail)
else:
trail.append(fractal_prices[i] if not np.isnan(fractal_prices[i]) else previous_trail)
previous_trail = trail[-1]
return pd.Series(trail, index=fractal_prices.index)
df['Long_Trailing_Stop'] = update_trailing_stop(df['Low_Fractal_Buffered'], df['Low_Fractal_Buffered'].iloc[0], df['Close'], direction='up')
df['Short_Trailing_Stop'] = update_trailing_stop(df['High_Fractal_Buffered'], df['High_Fractal_Buffered'].iloc[0], df['Close'], direction='down')
# 4. Flip Trend Logic
def flip_trend(price_data, long_trail, short_trail):
is_long = True
long_trail_output = []
short_trail_output = []
for i in range(len(price_data)):
if is_long and price_data[i] < short_trail[i] and not np.isnan(short_trail[i]):
is_long = False
elif not is_long and price_data[i] > long_trail[i] and not np.isnan(long_trail[i]):
is_long = True
long_trail_output.append(long_trail[i] if is_long else np.nan)
short_trail_output.append(short_trail[i] if not is_long else np.nan)
return pd.Series(long_trail_output, index=price_data.index), pd.Series(short_trail_output, index=price_data.index)
df['Long_Trail_Output'], df['Short_Trail_Output'] = flip_trend(df['Close'], df['Long_Trailing_Stop'], df['Short_Trailing_Stop'])
# 5. Plotting the Results
def plot_trailing_stops(df):
plt.figure(figsize=(14, 7))
plt.plot(df.index, df['Close'], label='Price', color='black')
plt.plot(df.index, df['Long_Trail_Output'], label='Long Stop Price', linestyle='--', color='green')
plt.plot(df.index, df['Short_Trail_Output'], label='Short Stop Price', linestyle='--', color='red')
plt.scatter(df.index, df['High_Fractal'], label='High Fractal', marker='v', color='blue')
plt.scatter(df.index, df['Low_Fractal'], label='Low Fractal', marker='^', color='orange')
plt.legend()
plt.show()
plot_trailing_stops(df)
``` from flask import Flask, request, jsonify, render_template, redirect, url_for
import pandas as pd
import numpy as np
import yfinance as yf
from datetime import datetime, timedelta
import matplotlib
matplotlib.use('Agg') # Use a non-GUI backend for Matplotlib
import matplotlib.pyplot as plt
import io
import base64
import os
app = Flask(__name__)
# Function to load data from Yahoo Finance
def load_stock_data(ticker, start_date, end_date):
stock = yf.download(ticker, start=start_date, end=end_date)
stock.reset_index(inplace=True)
return stock
# Function to format the ticker for Yahoo Finance
def format_ticker(ticker):
if ticker.isdigit(): # Korean stock ticker
return ticker + ".KS"
return ticker # US or other tickers
# DMI and ADX Calculation
def calculate_dmi_adx(data, period=14):
high = data['High']
low = data['Low']
close = data['Close']
plus_dm = high.diff()
minus_dm = low.diff()
plus_dm[plus_dm < 0] = 0
minus_dm[minus_dm > 0] = 0
tr1 = high - low
tr2 = abs(high - close.shift(1))
tr3 = abs(low - close.shift(1))
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
tr_smooth = tr.rolling(window=period).sum()
plus_dm_smooth = plus_dm.rolling(window=period).sum()
minus_dm_smooth = abs(minus_dm.rolling(window=period).sum())
plus_di = 100 * (plus_dm_smooth / tr_smooth)
minus_di = 100 * (minus_dm_smooth / tr_smooth)
dx = (abs(plus_di - minus_di) / (plus_di + minus_di)) * 100
adx = dx.rolling(window=period).mean()
return plus_di, minus_di, adx
@app.route('/')
def index():
return render_template('index.html')
@app.route('/analyze', methods=['POST'])
def analyze():
ticker = request.form['ticker']
ticker = format_ticker(ticker)
end_date = datetime.now()
start_date = end_date - timedelta(days=200)
data = load_stock_data(ticker, start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d'))
if data.empty:
return render_template('index.html', error="No data found for the given ticker. Please try again.")
# Apply DMI and ADX calculation
data['plus_di'], data['minus_di'], data['adx'] = calculate_dmi_adx(data)
# Calculate Bostian's IIX
data['bostian_iix'] = (data['Volume'] * ((data['Close'] - data['Low']) ** 2 - (data['High'] - data['Close']) ** 2) / (data['High'] - data['Low'])).cumsum()
# Volatility Analysis (Bollinger Bandwidth)
length = 20
source = data['Close']
basis = source.rolling(window=length).mean()
dev = source.rolling(window=length).std()
upper = basis + 2 * dev
lower = basis - 2 * dev
data['bandwidth'] = (upper - lower) / basis * 100
# Historical Mean and Standard Deviation
data['historical_mean'] = data['bandwidth'].rolling(window=length).mean()
data['historical_std'] = data['bandwidth'].rolling(window=length).std()
# Percentile Calculation
data['percentile'] = data['bandwidth'].rolling(window=length).apply(lambda x: np.sum(x <= x.iloc[-1]) / len(x) * 100, raw=False)
# Regime Classification
data['regime'] = np.where(data['bandwidth'] > (data['historical_mean'] + data['historical_std']), 'High',
np.where(data['bandwidth'] < (data['historical_mean'] - data['historical_std']), 'Low', 'Normal'))
# Plot Results
plt.figure(figsize=(14, 12))
# Plot Closing Price
plt.subplot(4, 1, 1)
plt.plot(data['Date'], data['Close'], label='Close Price', color='black')
plt.title('Stock Price')
plt.legend()
# Plot Bollinger Bandwidth
plt.subplot(4, 1, 2)
plt.plot(data['Date'], data['bandwidth'], label='Bandwidth', color='blue')
plt.plot(data['Date'], data['historical_mean'], label='Mean', color='green')
plt.plot(data['Date'], data['historical_mean'] + data['historical_std'], label='Mean + 1 Std Dev', color='red')
plt.plot(data['Date'], data['historical_mean'] - data['historical_std'], label='Mean - 1 Std Dev', color='red')
plt.title('Volatility Analysis (Bandwidth)')
plt.legend()
# Plot Bostian's IIX
plt.subplot(4, 1, 3)
plt.plot(data['Date'], data['bostian_iix'], label="Bostian's IIX", color='#2962FF')
plt.fill_between(data['Date'], data['bostian_iix'], color=np.where(data['bostian_iix'] >= 0, '#26A69A', '#B2DFDB'))
plt.title("Bostian's IIX Accumulated")
plt.legend()
# Plot Percentile of Bandwidth
plt.subplot(4, 1, 4)
plt.plot(data['Date'], data['percentile'], label='Bandwidth Percentile', color='purple')
plt.axhline(50, color='gray', linestyle='--', label='Median (50th Percentile)')
plt.title('Percentile of Bandwidth')
plt.legend()
plt.tight_layout()
# Save plot to a PNG image and encode it to base64
img = io.BytesIO()
plt.savefig(img, format='png')
img.seek(0)
plot_url = base64.b64encode(img.getvalue()).decode()
plt.close()
return render_template('result.html', plot_url=plot_url)
@app.route('/favicon.ico')
def favicon():
return '', 204
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
app.run(host="0.0.0.0", port=port, debug=False) ``` 이 코드를 보면 한국과 미국의 주식, etf, Index data를 yFinance에서 불러오는 backend code야. 우리가 만든 Python Trailing Stop Script 에 ' 한국과 미국의 주식, etf, Index data를 yFinance에서 불러오는 backend code '로 수정해 줄 수 있어?
``` <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stock Analysis</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
<h1 class="text-center">Stock, ETF, and Index Analysis</h1>
<form action="/analyze" method="POST" class="mt-4">
<div class="mb-3">
<label for="ticker" class="form-label">Enter Stock/ETF/Index Ticker</label>
<input type="text" id="ticker" name="ticker" class="form-control" placeholder="e.g., 005930 for Samsung, AAPL for Apple" required>
</div>
<button type="submit" class="btn btn-primary">Analyze</button>
</form>
<div class="mt-5 text-center">
<h3>Other Tools</h3>
<div class="d-grid gap-3 d-md-flex justify-content-md-center">
<a href="https://dmi-analysis-app-124-2011c455203e.herokuapp.com/" class="btn btn-secondary">DMI Analysis</a>
<a href="https://stocksnalysis-49edb39e1e84.herokuapp.com/" class="btn btn-secondary">Price Analysis</a>
<a href="https://bbhr-12e553aa0a18.herokuapp.com/" class="btn btn-secondary">Bollinger Bands Indicator</a>
</div>
</div>
</div>
</body>
</html> ``` 와 ``` <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Analysis Result</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
<h1 class="text-center">Analysis Result</h1>
<div class="text-center mt-4">
<img src="data:image/png;base64,{{ plot_url }}" alt="Analysis Plot" class="img-fluid">
</div>
<div class="text-center mt-4">
<a href="/" class="btn btn-primary">Analyze Another Ticker</a>
</div>
</div>
</body>
</html> ``` 은 바로 전에 참조한 backend code의 front Index.html이야, Python Trailing Stop Script ㅇ의 frontend Index.html 도 잘 만들어줄 수있어?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trailing Stop Analysis</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
<h1 class="text-center">Trailing Stop Analysis for Stocks, ETFs, and Indices</h1>
<form action="/analyze" method="POST" class="mt-4">
<div class="mb-3">
<label for="ticker" class="form-label">Enter Stock/ETF/Index Ticker</label>
<input type="text" id="ticker" name="ticker" class="form-control" placeholder="e.g., 005930 for Samsung, AAPL for Apple" required>
</div>
<button type="submit" class="btn btn-primary">Analyze</button>
</form>
<div class="mt-5 text-center">
<h3>Other Tools</h3>
<div class="d-grid gap-3 d-md-flex justify-content-md-center">
<a href="https://dmi-analysis-app-124-2011c455203e.herokuapp.com/" class="btn btn-secondary">DMI Analysis</a>
<a href="https://stocksnalysis-49edb39e1e84.herokuapp.com/" class="btn btn-secondary">Price Analysis</a>
<a href="https://bbhr-12e553aa0a18.herokuapp.com/" class="btn btn-secondary">Bollinger Bands Indicator</a>
</div>
</div>
</div>
</body>
</html>
이 frontend 와 backend code를 web service를 위해 Heroku 에 deploy 할 수 있는 코드로 만들어줘
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf
from flask import Flask, request, render_template, redirect, url_for
from datetime import datetime, timedelta
import io
import base64
import os
# Use a non-GUI backend for Matplotlib
plt.switch_backend('Agg')
# Create Flask app
app = Flask(__name__)
# Function to load data from Yahoo Finance
def load_stock_data(ticker, start_date, end_date):
stock = yf.download(ticker, start=start_date, end=end_date)
stock.reset_index(inplace=True)
return stock
# Function to format the ticker for Yahoo Finance
def format_ticker(ticker):
if ticker.isdigit(): # Korean stock ticker
return ticker + ".KS"
return ticker # US or other tickers
# Route for the index page
@app.route('/')
def index():
return render_template('index.html')
# Route for analyzing the stock data
@app.route('/analyze', methods=['POST'])
def analyze():
ticker = request.form['ticker']
ticker = format_ticker(ticker)
end_date = datetime.now()
start_date = end_date - timedelta(days=200)
df = load_stock_data(ticker, start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d'))
if df.empty:
return render_template('index.html', error="No data found for the given ticker. Please try again.")
# 1. Find Fractals
def find_fractals(df, left_range=2, right_range=2):
high_fractals = []
low_fractals = []
for i in range(left_range, len(df) - right_range):
is_high_fractal = all(df['High'][i] > df['High'][i - j] for j in range(1, left_range + 1)) and all(df['High'][i] > df['High'][i + j] for j in range(1, right_range + 1))
is_low_fractal = all(df['Low'][i] < df['Low'][i - j] for j in range(1, left_range + 1)) and all(df['Low'][i] < df['Low'][i + j] for j in range(1, right_range + 1))
high_fractals.append(df['High'][i] if is_high_fractal else np.nan)
low_fractals.append(df['Low'][i] if is_low_fractal else np.nan)
high_fractals = [np.nan] * left_range + high_fractals + [np.nan] * right_range
low_fractals = [np.nan] * left_range + low_fractals + [np.nan] * right_range
return pd.Series(high_fractals, index=df.index), pd.Series(low_fractals, index=df.index)
df['High_Fractal'], df['Low_Fractal'] = find_fractals(df)
# 2. Add Buffer to Fractals
def add_buffer(fractal_prices, buffer_percent, direction='minus'):
if direction == 'plus':
return fractal_prices * (1 + buffer_percent / 100)
elif direction == 'minus':
return fractal_prices * (1 - buffer_percent / 100)
else:
return fractal_prices
df['High_Fractal_Buffered'] = add_buffer(df['High_Fractal'], buffer_percent=2, direction='plus')
df['Low_Fractal_Buffered'] = add_buffer(df['Low_Fractal'], buffer_percent=2, direction='minus')
# 3. Update Trailing Stop
def update_trailing_stop(fractal_prices, previous_trail, price_data, direction='up'):
trail = []
for i in range(len(fractal_prices)):
if i == 0:
trail.append(fractal_prices[i] if not np.isnan(fractal_prices[i]) else price_data[i])
else:
if direction == 'down' and price_data[i] >= previous_trail:
trail.append(previous_trail)
elif direction == 'up' and price_data[i] <= previous_trail:
trail.append(previous_trail)
else:
trail.append(fractal_prices[i] if not np.isnan(fractal_prices[i]) else previous_trail)
previous_trail = trail[-1]
return pd.Series(trail, index=fractal_prices.index)
df['Long_Trailing_Stop'] = update_trailing_stop(df['Low_Fractal_Buffered'], df['Low_Fractal_Buffered'].iloc[0], df['Close'], direction='up')
df['Short_Trailing_Stop'] = update_trailing_stop(df['High_Fractal_Buffered'], df['High_Fractal_Buffered'].iloc[0], df['Close'], direction='down')
# 4. Flip Trend Logic
def flip_trend(price_data, long_trail, short_trail):
is_long = True
long_trail_output = []
short_trail_output = []
for i in range(len(price_data)):
if is_long and price_data[i] < short_trail[i] and not np.isnan(short_trail[i]):
is_long = False
elif not is_long and price_data[i] > long_trail[i] and not np.isnan(long_trail[i]):
is_long = True
long_trail_output.append(long_trail[i] if is_long else np.nan)
short_trail_output.append(short_trail[i] if not is_long else np.nan)
return pd.Series(long_trail_output, index=price_data.index), pd.Series(short_trail_output, index=price_data.index)
df['Long_Trail_Output'], df['Short_Trail_Output'] = flip_trend(df['Close'], df['Long_Trailing_Stop'], df['Short_Trailing_Stop'])
# Plotting the Results
plt.figure(figsize=(14, 7))
plt.plot(df.index, df['Close'], label='Price', color='black')
plt.plot(df.index, df['Long_Trail_Output'], label='Long Stop Price', linestyle='--', color='green')
plt.plot(df.index, df['Short_Trail_Output'], label='Short Stop Price', linestyle='--', color='red')
plt.scatter(df.index, df['High_Fractal'], label='High Fractal', marker='v', color='blue')
plt.scatter(df.index, df['Low_Fractal'], label='Low Fractal', marker='^', color='orange')
plt.legend()
plt.tight_layout()
# Save plot to a PNG image and encode it to base64
img = io.BytesIO()
plt.savefig(img, format='png')
img.seek(0)
plot_url = base64.b64encode(img.getvalue()).decode()
plt.close()
return render_template('result.html', plot_url=plot_url)
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
app.run(host="0.0.0.0", port=port, debug=False)
# Procfile
# web: python main.py
# requirements.txt
# Flask
# pandas
# numpy
# matplotlib
# yfinance
# gunicorn
``` c:\_Fractal Trail\app.py:115: UserWarning: Starting a Matplotlib GUI outside of the main thread will likely fail.
plt.figure(figsize=(14, 7))
[2024-11-30 19:51:14,771] ERROR in app: Exception on /analyze [POST] ``` ``` jinja2.exceptions.TemplateNotFound: result.html
127.0.0.1 - - [30/Nov/2024 19:51:14] "POST /analyze HTTP/1.1" 500 - ```
result.html 파일의 코드를 작성해드리길 원합니당
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Analysis Result</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
<h1 class="text-center">Analysis Result</h1>
<div class="text-center mt-4">
<img src="data:image/png;base64,{{ plot_url }}" alt="Analysis Plot" class="img-fluid">
</div>
<div class="text-center mt-4">
<a href="/" class="btn btn-primary">Analyze Another Ticker</a>
</div>
</div>
</body>
</html>
requirements.txt: 어떻게 구성할까?
Flask==2.0.3
Werkzeug==2.0.3
pandas==1.4.3
numpy==1.23.1
matplotlib==3.5.2
yfinance==0.1.70
gunicorn==20.1.0
Jinja2==3.1.2
``` remote: ! Currently the root directory of your app contains:
remote: !
remote: ! app.py
remote: ! apppy_good.py
remote: ! Procfile
remote: ! Requirements.txt
remote: ! runtime.txt
remote: ! templates/ ``` Requirements.txt 을 requirements.txt 으로 수정해서 올리려는데 어떻게 해?
파일 이름 변경:
mv Requirements.txt requirements.txt
Git에 변경 사항 추가:
git add requirements.txt
git rm Requirements.txt
커밋:
git commit -m "Rename Requirements.txt to requirements.txt"