@@ -1042,6 +1042,191 @@ def Resistance(self, high_idx=2, period=14):
10421042 self .window [- 1 ].append (resistance_level )
10431043 return self .window
10441044
1045+ # Parabolic SAR
1046+
1047+ def PSAR (self , HighIDX = 2 , LowIDX = 3 , startAF = 0.02 , stepAF = 0.02 , maxAF = 0.2 ):
1048+ """
1049+ Compute Parabolic SAR for the current window, fully transparent,
1050+ and append all intermediate values as new columns:
1051+ SAR, Trend (1=up, -1=down), Extreme Point (EP), Acceleration Factor (AF)
1052+ """
1053+ n = len (self .window )
1054+
1055+ # Ensure enough data to compute
1056+ if n < 2 or self .window [- 2 ][HighIDX ] is None or self .window [- 2 ][LowIDX ] is None :
1057+ self .AddColumn (None ) # SAR
1058+ self .AddColumn (None ) # Trend
1059+ self .AddColumn (None ) # EP
1060+ self .AddColumn (None ) # AF
1061+ return self .window
1062+
1063+ prev_row = self .window [- 2 ]
1064+ curr_row = self .window [- 1 ]
1065+
1066+ sarIDX = len (prev_row )- 4
1067+
1068+ # Read previous SAR, trend, EP, AF if they exist
1069+ prev_sar = prev_row [sarIDX ] if prev_row [sarIDX ] is not None else prev_row [LowIDX ] # initial guess
1070+ prev_trend = prev_row [sarIDX + 1 ] if len (prev_row ) > sarIDX + 1 and prev_row [sarIDX + 1 ] is not None else 1 # assume uptrend
1071+ prev_ep = prev_row [sarIDX + 2 ] if len (prev_row ) > sarIDX + 2 and prev_row [sarIDX + 2 ] is not None else prev_row [HighIDX ]
1072+ prev_af = prev_row [sarIDX + 3 ] if len (prev_row ) > sarIDX + 3 and prev_row [sarIDX + 3 ] is not None else startAF
1073+
1074+ # Determine current trend
1075+ trend = prev_trend
1076+ ep = prev_ep
1077+ af = prev_af
1078+ sar = prev_sar
1079+
1080+ # Update SAR
1081+ sar = sar + af * (ep - sar )
1082+
1083+ # Check for trend reversal
1084+ if trend == 1 : # Uptrend
1085+ if curr_row [LowIDX ] < sar :
1086+ trend = - 1
1087+ sar = ep
1088+ ep = curr_row [LowIDX ]
1089+ af = startAF
1090+ else :
1091+ if curr_row [HighIDX ] > ep :
1092+ ep = curr_row [HighIDX ]
1093+ af = min (af + stepAF , maxAF )
1094+ else : # Downtrend
1095+ if curr_row [HighIDX ] > sar :
1096+ trend = 1
1097+ sar = ep
1098+ ep = curr_row [HighIDX ]
1099+ af = startAF
1100+ else :
1101+ if curr_row [LowIDX ] < ep :
1102+ ep = curr_row [LowIDX ]
1103+ af = min (af + stepAF , maxAF )
1104+
1105+ # Append results as new columns
1106+ self .AddColumn (sar ) # SAR
1107+ self .AddColumn (trend ) # Trend: 1=up, -1=down
1108+ self .AddColumn (ep ) # Extreme Point
1109+ self .AddColumn (af ) # Acceleration Factor
1110+
1111+ return self .window
1112+
1113+ # Momentum indicator
1114+
1115+ def Momentum (self , colIDX , period = 10 ):
1116+ """
1117+ Calculate momentum for a given column index and period.
1118+ Momentum = Current value - value N periods ago
1119+ Appends the result as a new column in the rolling window.
1120+
1121+ :param colIDX: int, the column index in the rolling window to calculate momentum on
1122+ :param period: int, number of periods to look back
1123+ """
1124+
1125+ # Get the last row
1126+ last_row = self .LastRow ()
1127+
1128+ # Ensure there is enough history
1129+ if len (self .window ) < period + 1 :
1130+ self .AddColumn (None )
1131+ return self .window
1132+
1133+ # Calculate momentum
1134+ if len (self .window [- (period + 1 )])< colIDX + 1 :
1135+ self .AddColumn (None )
1136+ return self .window
1137+
1138+ past_value = self .window [- (period + 1 )][colIDX ]
1139+ if past_value is None :
1140+ self .AddColumn (None )
1141+ return self .window
1142+
1143+ current_value = last_row [colIDX ]
1144+
1145+ if past_value is not None and current_value is not None :
1146+ momentum = current_value - past_value
1147+ else :
1148+ momentum = None
1149+
1150+ # Add momentum as new column
1151+ self .AddColumn (momentum )
1152+ return self .window
1153+
1154+ def RateOfChange (self , colIDX , period = 10 ):
1155+ """
1156+ Calculate the Rate of Change (ROC) for a given column index over a specified period.
1157+ ROC = ((current value - value N periods ago) / value N periods ago) * 100
1158+ The result is appended as a new column to the rolling window.
1159+
1160+ Parameters:
1161+ colIDX (int): Column index to calculate ROC from
1162+ period (int): Number of periods for the ROC calculation
1163+ """
1164+ last_row = self .LastRow ()
1165+
1166+ # Check if enough rows exist
1167+ if len (self .window ) > period :
1168+ past_row = self .window [- period - 1 ] # Row N periods ago
1169+ if len (past_row )< colIDX + 1 :
1170+ roc = None
1171+ else :
1172+ current_value = last_row [colIDX ]
1173+ past_value = past_row [colIDX ]
1174+
1175+ if current_value is not None and past_value not in (None , 0 ):
1176+ roc = ((current_value - past_value ) / past_value ) * 100
1177+ else :
1178+ roc = None
1179+ else :
1180+ roc = None
1181+
1182+ # Add ROC as a new column
1183+ self .AddColumn (roc )
1184+ return self .window
1185+
1186+ # On Balance Volume
1187+
1188+ def OBV (self , closeIDX = 4 , volumeIDX = 5 ):
1189+ """
1190+ Calculate On-Balance Volume (OBV) based on closing prices and volume.
1191+ OBV increases by volume when the closing price rises,
1192+ decreases by volume when the closing price falls,
1193+ remains unchanged if the price is the same.
1194+
1195+ Parameters:
1196+ closeIDX (int): Column index of the closing price
1197+ volumeIDX (int): Column index of the volume
1198+ """
1199+ if len (self .window )< 1 :
1200+ self .AddColumn (None )
1201+ return self .window
1202+
1203+ last_row = self .LastRow ()
1204+ prevIDX = len (last_row ) # Get the right idx for the OBV
1205+ prev_row = self .window [- 2 ] # Previous row
1206+
1207+ close_now = last_row [closeIDX ]
1208+ close_prev = prev_row [closeIDX ]
1209+ vol_now = last_row [volumeIDX ]
1210+ if len (prev_row )< prevIDX + 1 :
1211+ self .AddColumn (vol_now )
1212+ return self .window
1213+
1214+ obv_prev = prev_row [prevIDX ] if prev_row [prevIDX ] is not None else vol_now # Use last column as previous OBV
1215+
1216+ if close_now is not None and close_prev is not None and vol_now is not None :
1217+ if close_now > close_prev :
1218+ obv = obv_prev + vol_now
1219+ elif close_now < close_prev :
1220+ obv = obv_prev - vol_now
1221+ else :
1222+ obv = obv_prev
1223+ else :
1224+ obv = vol_now
1225+
1226+ # Add OBV as a new column
1227+ self .AddColumn (obv )
1228+ return self .window
1229+
10451230###
10461231### END of code
10471232###
0 commit comments