├── .gitignore ├── LICENSE ├── README.md └── python ├── LSTM.py ├── backtest_vbt.py ├── backtesting_bt.py ├── backtesting_vbt.py ├── credentials └── info.txt ├── ks_API.py ├── nse_py.py ├── program.py ├── program_test.py ├── requirments.txt ├── technical_analysis.py ├── test.py ├── trading_platform.py ├── trading_strategy.py └── vbt_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | old/ 2 | python/credentials/*.py 3 | *.pyc 4 | *.db 5 | *.pkl 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Vinay Ram Gazula 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AlgoTrade-API 2 | Fully automated NSE Stock/Equity trading Bot integrated with Kotak Securities Broker API. 3 | 4 | ## Under Development 5 | -------------------------------------------------------------------------------- /python/LSTM.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pandas as pd 4 | import tensorflow as tf 5 | from collections import deque 6 | import matplotlib.pyplot as plt 7 | from sklearn.preprocessing import MinMaxScaler 8 | from tensorflow.keras.models import Sequential 9 | from tensorflow.keras.layers import Dense, LSTM 10 | from sklearn.model_selection import train_test_split 11 | from tensorflow.keras.callbacks import ModelCheckpoint 12 | 13 | 14 | class LSTM_DL(object): 15 | def __init__(self, Ticker, Data, Dataset = "Close", delta = 0.9, time_step = 30, epochs = 100): 16 | self.Data = Data 17 | self.delta = delta # training percent 18 | self.Ticker = Ticker 19 | self.epochs = epochs 20 | self.time_step = time_step # lookback period 21 | self.Dataset = Data.filter([Dataset]).values 22 | self.callback_path = "./LSTM_checkpoint/" + Ticker + ".ckpt" 23 | return 24 | 25 | 26 | @property 27 | def ScaleData(self): 28 | if hasattr(self, "data_is_scaled"): 29 | return self.ScaledData 30 | else: 31 | self.scaler = MinMaxScaler(feature_range=(0,1)) 32 | scaled_data = self.scaler.fit_transform(self.Dataset) 33 | self.ScaledData = scaled_data 34 | self.data_is_scaled = True 35 | return self.ScaledData 36 | 37 | 38 | def InverseTransform(self, Dataset): 39 | transformed = self.scaler.inverse_transform(Dataset) 40 | return transformed 41 | 42 | 43 | @property 44 | def Split_XY(self): 45 | X = []; Y = [] 46 | time_step = self.time_step 47 | scaled_data = self.ScaleData 48 | for i in range(time_step, len(scaled_data)): 49 | X.append(scaled_data[i-time_step:i,0]) 50 | Y.append(scaled_data[i,0]) 51 | return X, Y 52 | 53 | 54 | def Split_train_test(self): 55 | if hasattr(self, "data_is_split"): 56 | return x_train, x_test, y_train, y_test 57 | else: 58 | self.X, self.Y = self.Split_XY 59 | x_train, x_test, y_train, y_test = train_test_split(self.X, self.Y, train_size = self.delta, shuffle = False) 60 | x_train = np.array(x_train) 61 | x_test = np.array(x_train) 62 | y_train = np.array(x_train) 63 | y_test = np.array(x_train) 64 | return x_train, x_test, y_train, y_test 65 | 66 | 67 | @property 68 | def RNN(self): 69 | self.x_train, self.x_test, self.y_train, self.y_test = self.Split_train_test() 70 | model = Sequential() 71 | model.add(LSTM(50, return_sequences = True, input_shape = (self.time_step, 1))) 72 | model.add(LSTM(50, return_sequences = False)) 73 | model.add(Dense(25)) 74 | model.add(Dense(1)) 75 | model.compile(optimizer = "adam", loss = "mean_squared_error", metrics = ["accuracy"]) 76 | if hasattr(self, "_modeled") or (self.Ticker + ".ckpt.index") in os.listdir("./LSTM_checkpoint/"): 77 | #latest_cp = tf.train.latest_checkpoint(self.callback_path) 78 | model.load_weights(self.callback_path) 79 | else: 80 | model.fit(self.x_train, self.y_train, batch_size = 1, epochs = self.epochs, validation_data = (self.x_test, self.y_test), callbacks = [self.Callback]) 81 | _modeled = True 82 | print(model.summary()) 83 | loss, acc = model.evaluate(self.x_test, self.y_test, verbose=2) 84 | print("Restored model, accuracy: {:5.2f}%".format(100 * acc)) 85 | return model 86 | 87 | 88 | @property 89 | def Callback(self): 90 | callback = ModelCheckpoint(filepath = self.callback_path, save_weights_only = True, verbose = 1) 91 | return callback 92 | 93 | 94 | def Prediction(self, model, N): 95 | pred_data = [] 96 | historic_data = deque(self.ScaleData[-self.time_step:,:], maxlen = self.time_step) 97 | for i in range(N): 98 | x_data = [] 99 | x_data.append(list(historic_data)) 100 | x_data = np.array(x_data) 101 | x_data = np.reshape(x_data, (x_data.shape[0], x_data.shape[1], 1)) 102 | pred_value = model.predict(x_data) 103 | historic_data.append(np.array(pred_value[0])) 104 | pred_data.append(pred_value[0]) 105 | pred_prices = self.InverseTransform(np.array(pred_data)) 106 | return pred_prices[:,0].tolist() 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /python/backtest_vbt.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import vectorbt as vbt 3 | from backtesting_vbt import BackTestingEngine 4 | 5 | 6 | bte = BackTestingEngine() 7 | 8 | 9 | tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'NVDA', 'WMT', 'BAC', 'KO', 'DIS', 'PFE', 'FB', 'BABA', 'MA', 'V', 'TM', 'PEP', 'AMD', 'INTC', 'NKE', 'ORCL', 'ACN'] 10 | 11 | tickers += ["ITC.NS", "TRIDENT.NS", "BRITANNIA.NS", "MARUTI.NS", "MRF.NS", "ADANIPORTS.NS", "EICHERMOT.NS", "TITAN.NS", "TATAMOTORS.NS", "ONGC.NS", "NTPC.NS", "LT.NS", "TATASTEEL.NS", "WIPRO.NS", "SBIN.NS", "AXISBANK.NS", "ICICIBANK.NS", "BPCL.NS", "HDFCBANK.NS", "IOC.NS"] 12 | 13 | #tickers = ['MSFT'] 14 | 15 | 16 | def load_financial_data(output_file, period, tickers): 17 | try: 18 | df = pd.read_pickle(output_file) 19 | print('File data found...') 20 | except FileNotFoundError: 21 | print('File data not found...downloading the data') 22 | df = vbt.YFData.download(tickers, missing_index='drop', interval="1D", period = period).get('Close') 23 | df.to_pickle(output_file) 24 | return df 25 | 26 | 27 | def postprocess(data, Pf): 28 | values = {} 29 | for i in range(len(data.columns)): 30 | column_name = data.columns[i] 31 | stat = Pf.stats(column = i) 32 | values[column_name] = stat 33 | strat_res = Pf.stats(group_by = True) 34 | returns = Pf.total_return() 35 | #print(returns.to_string()) 36 | total_values = Pf.stats(agg_func = None) 37 | print(strat_res) 38 | print(returns.max(), "--->", returns.idxmax()) 39 | print(returns.min(), "--->", returns.idxmin()) 40 | #print(total_values[["Start Value", "End Value", "Total Return [%]", "Sharpe Ratio", "Sortino Ratio"]].to_string()) 41 | return total_values, values 42 | 43 | 44 | def bt_Double_N(data, N, MA_span): 45 | dN = bte.vbt_Double_N 46 | res = dN.run(data, N = N, MA_span = MA_span) 47 | entries = res.signal == 1.0 48 | exits = res.signal == -1.0 49 | pf = vbt.Portfolio.from_signals(data, entries, exits, freq='d') 50 | total_values, values = postprocess(data, pf) 51 | return pf, values 52 | 53 | 54 | def bt_nPeriod_RSI(data, N, MA_span, LL, UL): 55 | nRSI = bte.vbt_nPeriod_RSI 56 | res = nRSI.run(data, N = N, MA_span = MA_span, LL = LL, UL = UL) 57 | entries = res.signal == 1.0 58 | exits = res.signal == -1.0 59 | pf = vbt.Portfolio.from_signals(data, entries, exits, freq='d') 60 | total_values, values = postprocess(data, pf) 61 | return pf, values 62 | 63 | 64 | def bt_Cumulative_RSI(data, X, Y, N, LL, UL, sl_stop): 65 | cRSI = bte.vbt_Cumulative_RSI 66 | res = cRSI.run(data, X = X, Y = Y, N = N, LL = LL, UL = UL) 67 | entries = res.signal == 1.0 68 | exits = res.signal == -1.0 69 | pf = vbt.Portfolio.from_signals(data, entries, exits, freq='d', sl_stop = sl_stop) 70 | total_values, values = postprocess(data, pf) 71 | return pf, values 72 | 73 | 74 | def bt_HMA_Crossover(data, short_span, long_span): 75 | HMA = bte.vbt_HMA_Crossover 76 | res = HMA.run(data, short_span = short_span, long_span = long_span) 77 | entries = res.signal == 1.0 78 | exits = res.signal == -1.0 79 | pf = vbt.Portfolio.from_signals(data, entries, exits, freq='d') 80 | #total_values, values = postprocess(data, pf) 81 | values = None 82 | return pf, values 83 | 84 | 85 | 86 | 87 | close_price = load_financial_data("BackTest_DATA/data.pkl", period = "20y", tickers = tickers) 88 | dN_pf, dN_res = bt_Double_N(close_price, N = [5,6,7], MA_span = [100,200]) 89 | #nRSI_pf, nRSI_res = bt_nPeriod_RSI(close_price, N = [2,3,4], MA_span = [100,200], LL = [10,20,30], UL = [70,80,90]) 90 | #cRSI_pf, cRSI_res = bt_Cumulative_RSI(close_price, X = [2], Y = [4], N = [2], LL = [10], UL = [80], sl_stop = 0.01) 91 | #HMA_pf, HMA_res = bt_HMA_Crossover(close_price, short_span = [10,20,30], long_span = [60,70,80]) 92 | 93 | 94 | #fig = HMA_pf.total_return().vbt.heatmap(x_level = "HMA_short_span", y_level = "HMA_long_span") 95 | #fig.show() 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /python/backtesting_bt.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import yfinance as yf 3 | import backtrader as bt 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | #WARNING backtrader is not available for python version 3.10.4 so we should continue with vectorbt using the available code. 8 | 9 | -------------------------------------------------------------------------------- /python/backtesting_vbt.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import vectorbt as vbt 4 | from pyti.hull_moving_average import hull_moving_average as hma 5 | from pyti.weighted_moving_average import weighted_moving_average as wma 6 | 7 | 8 | 9 | class BackTestingEngine(): 10 | def __init__(self): 11 | self.long_dataset = "long" 12 | self.short_dataset = "short" 13 | return 14 | 15 | 16 | def Plot(self, pf, title): 17 | plt = pf.plot() 18 | plt.layout.title = title 19 | plt.show() 20 | return 21 | 22 | 23 | def CustomIndicator(self, Data, Dataset = "Close", plot = False, title = ""): 24 | entries = Data[self.long_dataset] == 1.0 25 | exits = Data[self.short_dataset] == 1.0 26 | pf = vbt.Portfolio.from_signals(Data[Dataset], entries, exits) 27 | if plot: 28 | self.Plot(pf, title) 29 | return pf 30 | 31 | 32 | def bt_Double_N(self, Dataset, N = 7, MA_span = 100): 33 | N_max = Dataset.rolling(window = N).max() 34 | N_min = Dataset.rolling(window = N).min() 35 | ma = Dataset.rolling(window = MA_span).mean() 36 | signal = np.where(Dataset == N_min, 1.0, 0.0) 37 | signal = np.where(Dataset < ma, -1.0, signal) 38 | return signal 39 | 40 | 41 | @property 42 | def vbt_Double_N(self): 43 | double_N = vbt.IndicatorFactory( 44 | class_name = "Double_N", 45 | short_name = "double_N", 46 | input_names = ["Close"], 47 | param_names = ["N", "MA_span"], 48 | output_names = ["signal"] 49 | ).from_apply_func(self.bt_Double_N, N = 7, MA_span = 100, 50 | keep_pd = True, param_product = True) 51 | return double_N 52 | 53 | 54 | def bt_nPeriod_RSI(self, Dataset, N = 2, MA_span = 100, LL = 30, UL = 70): 55 | rsi = vbt.RSI.run(Dataset, window = N, ewm = True) 56 | ma = Dataset.rolling(window = MA_span).mean() 57 | signal = np.where(rsi.rsi <= LL, 1.0, 0.0) 58 | signal = np.where(ma > Dataset, -1.0, signal) 59 | return signal 60 | 61 | 62 | @property 63 | def vbt_nPeriod_RSI(self): 64 | nPeriod_RSI = vbt.IndicatorFactory( 65 | class_name = "nPeriod_RSI", 66 | short_name = "nP_RSI", 67 | input_names = ["Close"], 68 | param_names = ["N", "MA_span", "LL", "UL"], 69 | output_names = ["signal"] 70 | ).from_apply_func(self.bt_nPeriod_RSI, N = 2, MA_span = 100, LL = 30, UL = 70, keep_pd = True, param_product = True) 71 | return nPeriod_RSI 72 | 73 | 74 | def bt_Cumulative_RSI(self, Dataset, X = 2, Y = 4, N = 2, LL = 10, UL = 90): 75 | rsi = vbt.RSI.run(Dataset, window = N, ewm = True) 76 | crsi = rsi.rsi.rolling(window = X).sum() 77 | signal = np.where((crsi <= Y*LL) | (rsi.rsi <= LL), 1.0, 0.0) 78 | signal = np.where((crsi >= UL) | (rsi.rsi >= UL), -1.0, signal) 79 | return signal 80 | 81 | 82 | @property 83 | def vbt_Cumulative_RSI(self): 84 | Cumulative_RSI = vbt.IndicatorFactory( 85 | class_name = "Cumulative_RSI", 86 | short_name = "CRSI", 87 | input_names = ["Close"], 88 | param_names = ["X", "Y", "N", "LL", "UL"], 89 | output_names = ["signal"] 90 | ).from_apply_func(self.bt_Cumulative_RSI, X = 2, Y = 4, N = 2, LL = 10, UL = 90, keep_pd = True, param_product = True) 91 | return Cumulative_RSI 92 | 93 | 94 | def bt_HMA_Crossover(self, Dataset, short_span = 20, long_span = 60): 95 | #print(Dataset);exit() 96 | if len(Dataset.columns) > 1: 97 | for i in range(len(Dataset.columns)): 98 | column_name = Dataset.columns[i] 99 | MA_slow = hma(Dataset[column_name], long_span) 100 | MA_fast = hma(Dataset[column_name], short_span) 101 | MA_slow = np.array(MA_slow) 102 | MA_fast = np.array(MA_fast) 103 | signal = np.where(MA_fast > MA_slow, 1.0, 0.0) 104 | signal = np.where(MA_fast < MA_slow, -1.0, signal) 105 | return signal 106 | else: 107 | MA_slow = hma(Dataset["Close"], long_span) 108 | MA_fast = hma(Dataset["Close"], short_span) 109 | MA_slow = np.array(MA_slow) 110 | MA_fast = np.array(MA_fast) 111 | signal = np.where(MA_fast > MA_slow, 1.0, 0.0) 112 | signal = np.where(MA_fast < MA_slow, -1.0, signal) 113 | return signal 114 | 115 | 116 | @property 117 | def vbt_HMA_Crossover(self): 118 | HMA_Crossover = vbt.IndicatorFactory( 119 | class_name = "HMA_Crossover", 120 | short_name = "HMA", 121 | input_names = ["Close"], 122 | param_names = ["short_span", "long_span"], 123 | output_names = ["signal"] 124 | ).from_apply_func(self.bt_HMA_Crossover, short_span = 20, long_span = 60, keep_pd = True, param_product = True) 125 | return HMA_Crossover 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /python/credentials/info.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | class KSAPI_Credentials(): 4 | def __init__(self): 5 | self.access_token = "###" 6 | self.userid = "###" 7 | self.password = "###" 8 | self.consumer_key = "###" 9 | self.consumer_secret_key = "###" 10 | self.ip_address = "0.0.0.0" 11 | self.app_id = "DefaultApplication" 12 | return 13 | -------------------------------------------------------------------------------- /python/ks_API.py: -------------------------------------------------------------------------------- 1 | import json 2 | import urllib3 3 | import requests 4 | import pandas as pd 5 | import datetime as dt 6 | from ks_api_client import ks_api 7 | from credentials.info import KSAPI_Credentials 8 | from trading_platform import TradingPlatform 9 | from ks_api_client.models.tsloplace import Tsloplace 10 | 11 | 12 | class KSTrade_API(TradingPlatform): 13 | def __init__(self): 14 | #Initiate TradingPlatform class 15 | super(KSTrade_API, self).__init__() 16 | 17 | """ 18 | try: 19 | with open("credentials.txt") as file: 20 | credentials = [line.rstrip() for line in file] # Opens the credentials file, reads all the lines and strips off the \n at the end of the lines 21 | except Exception as ex: 22 | print("credentials not found:", ex) 23 | pass 24 | """ 25 | 26 | credentials = KSAPI_Credentials() 27 | self.access_token = credentials.access_token 28 | self.userid = credentials.userid 29 | self.password = credentials.password 30 | self.consumer_key = credentials.consumer_key 31 | self.consumer_secret_key = credentials.consumer_secret_key 32 | self.ip_address = credentials.ip_address 33 | self.app_id = credentials.app_id 34 | self.scripmaster_url = "/scripmaster/1.1/filename" 35 | self.margin_url = "/margin/1.0/margin" 36 | self.watchlists_url = "/watchlist/2.1/watchlists" 37 | self.portfolio_url = "/portfolio/1.0/portfolio/holdings/all" 38 | self.session_2fa_url = "/session/1.0/session/2FA/oneTimeToken" 39 | #self.OTP = "3340" # We can bypass this step by using oneTimeToken system 40 | return 41 | 42 | 43 | # SessionLogin 44 | def Session_init(self): 45 | urllib3.disable_warnings() # This disables the warnings that pop up on terminal 46 | # Defining the host is optional and defaults to https://sbx.kotaksecurities.com/apim 47 | # See configuration.py for a list of all supported configuration parameters. 48 | client = ks_api.KSTradeApi(access_token = self.access_token, userid = self.userid, consumer_key = self.consumer_key,ip = self.ip_address, app_id = self.app_id) 49 | # Initiate login and generate OTT 50 | client.login(password = self.password) 51 | #client.session_2fa(access_code = self.OTP) # We can bypass this step by using oneTimeToken system 52 | 53 | # TwoFactorAuthentication 54 | header = {"accept ": "application/json", "oneTimeToken " : f"{client.one_time_token}", "consumerKey " : f"{client.consumer_key}", "ip " : f"{client.ip}", 55 | "appId " : f"{client.app_id}", "Content-Type " : "application/json", "Authorization" : f"Bearer {client.access_token}"} 56 | data = '{"userid": "' + client.userid + '"}' 57 | link = client.host + self.session_2fa_url 58 | response = requests.post(url = link, headers = header, data = data).json() 59 | client.session_token = response["success"]["sessionToken"] 60 | print("Session initiated at", dt.datetime.now()) 61 | return client 62 | 63 | 64 | @property 65 | def Client(self): 66 | if hasattr(self, "session_initiated"): 67 | return self.client 68 | else: 69 | client = self.Session_init() 70 | self.client = client 71 | self.session_initiated = True 72 | return self.client 73 | 74 | 75 | # WatchlistName 76 | def WatchlistName_req(self): 77 | client = self.Client 78 | header = {"accept ": "application/json", "userid " : f"{client.userid}", "consumerKey " : f"{client.consumer_key}", "sessionToken" : f"{client.session_token}", 79 | "Authorization" : f"Bearer {client.access_token}"} 80 | link = client.host + self.watchlists_url 81 | response = requests.get(url = link, headers = header).json() 82 | lists = response["Success"] 83 | list_name = [] 84 | for name in lists: 85 | list_name += [name["watchlistName"]] 86 | return list_name 87 | 88 | 89 | # WatchlistData 90 | def Watchlist_req(self, list_name): 91 | client = self.Client 92 | header = {"accept ": "application/json", "userid " : f"{client.userid}", "consumerKey " : f"{client.consumer_key}", "sessionToken" : f"{client.session_token}", 93 | "Authorization" : f"Bearer {client.access_token}"} 94 | link = client.host + self.watchlists_url + "/byName/" + list_name 95 | response = requests.get(url = link, headers = header).json() 96 | symbols = response["Success"] 97 | print(json.dumps(symbols, indent = 2)) 98 | return symbols 99 | 100 | 101 | # PortfolioHoldings 102 | def Portfolio_req(self): 103 | client = self.Client 104 | header = {"accept ": "application/json", "consumerKey " : f"{client.consumer_key}", "sessionToken" : f"{client.session_token}", 105 | "Authorization" : f"Bearer {client.access_token}"} 106 | link = client.host + self.portfolio_url 107 | response = requests.get(url = link, headers = header).json() 108 | holdings = response["Success"] 109 | return holdings 110 | 111 | 112 | # Scripmaster 113 | def Scripmaster_req(self): 114 | client = self.Client 115 | header = {"accept ": "application/json", "consumerKey " : f"{client.consumer_key}", "Authorization" : f"Bearer {client.access_token}"} 116 | link = client.host + self.scripmaster_url 117 | response = requests.get(url = link, headers = header).json() 118 | #fno_url = response["Success"]["fno"]; fno_df = pd.read_csv(fno_url, sep="|") 119 | cash_url = response["Success"]["cash"] 120 | cash_df = pd.read_csv(cash_url, sep = "|") 121 | NSE_equity = cash_df[(cash_df.exchange == "NSE") & (cash_df.instrumentType == "EQ")] 122 | tokens = NSE_equity.instrumentToken 123 | names = NSE_equity.instrumentName 124 | assert len(names) == len(tokens) 125 | #ltp = NSE_equity.lastPrice 126 | data = {} 127 | for i in range(len(tokens)): 128 | data[names[i]] = tokens[i] 129 | return data 130 | 131 | 132 | # MarginAvailable 133 | @property 134 | def Margin_req(self): 135 | """ 136 | header = {"accept ": "application/json", "consumerKey " : f"{client.consumer_key}", "sessionToken" : f"{client.session_token}", 137 | "Authorization" : f"Bearer {client.access_token}"} 138 | link = client.host + self.margin_url 139 | response = requests.get(url = link, headers = header).json() 140 | currency = response["Success"]["currency"] 141 | derivatives = response["Success"]["derivatives"] 142 | 143 | Instead of this we can use client.margin() in order to get this data 144 | """ 145 | client = self.Client 146 | margin = client.margin() 147 | equity = margin["Success"]["equity"][0] 148 | #print(dt.datetime.now()) 149 | #print(json.dumps(equity, indent = 2)) 150 | CashBalance = margin["Success"]["equity"][0]["cash"]["availableCashBalance"] 151 | return CashBalance 152 | 153 | 154 | def PlaceOrder(self, *args, **kwargs): 155 | client = self.Client 156 | try: 157 | Order = client.place_order(*args, **kwargs) 158 | return Order 159 | except Exception as ex: 160 | return ex 161 | 162 | 163 | def TStop_loss(self, spread = 2, trailingPrice = 0): 164 | client = self.Client 165 | model = Tsloplace(spread = spread, trailingPrice = trailingPrice) 166 | return model 167 | 168 | 169 | def Run(self, OrderType = "MIS", qty = 1, price = 0, StopLoss = True): 170 | TickerPosition = self.Get_TickerPosition 171 | Instrument_tokens = self.Scripmaster_req() 172 | margin = self.Margin_req 173 | for ticker in TickerPosition: 174 | Price = TickerPosition[ticker][1] 175 | if StopLoss: 176 | trigger_price = Price - 2.0 177 | else: 178 | trigger_price = 0 179 | Position = TickerPosition[ticker][0] 180 | if Position == "BUY": 181 | print("Buy", ticker, Instrument_tokens[ticker], " @ ", TickerPosition[ticker][1]) 182 | #if Price <= (margin*4): 183 | #try: 184 | #order = self.PlaceOrder(order_type = OrderType, instrument_token = Instrument_tokens[ticker], transaction_type = "BUY", quantity = qty, price = price, disclosed_quantity = 0, trigger_price = trigger_price, tag = "", validity = "GFD", variety = "INTRADAY") 185 | #print(order) 186 | #except Exception as ex: 187 | #print(ex) 188 | elif Position == "SELL": 189 | print("Sell", ticker, Instrument_tokens[ticker], " @ ", TickerPosition[ticker][1]) 190 | #try: 191 | #order = self.PlaceOrder(order_type = OrderType, instrument_token = Instrument_tokens[ticker], transaction_type = "SELL", quantity = qty, price = price, disclosed_quantity = 0, trigger_price = trigger_price, tag = "", validity = "GFD", variety = "INTRADAY") 192 | #print(order) 193 | #except Exception as ex: 194 | #print(ex) 195 | else: 196 | pass 197 | return 198 | 199 | 200 | 201 | # ---------------------------------------------------------------------------- # 202 | 203 | 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /python/nse_py.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from nsepy import get_history 3 | from nsepy.symbols import get_symbol_list 4 | from nsepy.symbols import get_index_constituents_list 5 | from datetime import date 6 | 7 | 8 | sbin = get_history(symbol='SBIN', start=date(2015,1,1), end=date(2015,1,10)) 9 | print(sbin) 10 | 11 | 12 | NSE_INDICES = ["NIFTY 50","NIFTY NEXT 50","NIFTY100 LIQ 15","NIFTY 100","NIFTY 200","NIFTY 500","NIFTY MIDCAP 50","NIFTY MIDCAP 100","NIFTY SMALL 100","NIFTY AUTO","NIFTY BANK","NIFTY ENERGY","NIFTY FIN SERVICE","NIFTY FMCG","NIFTY IT","NIFTY MEDIA","NIFTY METAL","NIFTY PHARMA","NIFTY PSU BANK","NIFTY REALTY","NIFTY COMMODITIES","NIFTY CONSUMPTION","NIFTY CPSE","NIFTY INFRA","NIFTY MNC","NIFTY PSE","NIFTY SERV SECTOR","NIFTY SHARIAH 25","NIFTY50 SHARIAH","NIFTY500 SHARIAH","NIFTY100 EQUAL WEIGHT","NIFTY50 USD","NIFTY50 DIV POINT","NIFTY DIV OPPS 50","NIFTY ALPHA 50","NIFTY HIGH BETA 50","NIFTY LOW VOLATILITY 50","NIFTY QUALITY 30","NIFTY50 VALUE 20","NIFTY GROWSECT 15","NIFTY50 TR 2X LEV","NIFTY50 TR 1X INV"] 13 | 14 | 15 | symbol_list = get_symbol_list() 16 | #print(symbol_list) 17 | 18 | nifty50 = get_index_constituents_list(index="NIFTY50")["Symbol"].to_list() 19 | nifty100 = get_index_constituents_list(index="NIFTY100")["Symbol"].to_list() 20 | nifty500 = get_index_constituents_list(index="NIFTY500")["Symbol"].to_list() 21 | 22 | 23 | path = "~/Downloads/" 24 | 25 | n_50 = pd.read_csv(path + "NIFTY-50.csv") 26 | #n_50.drop(n_50.index[0], axis = 0, inplace=True) 27 | syms_50_nse = n_50["SYMBOL \n"].to_list() 28 | 29 | n_100 = pd.read_csv(path + "NIFTY-100.csv") 30 | #n_100.drop(n_100.index[0], axis = 0, inplace=True) 31 | syms_100_nse = n_100["SYMBOL \n"].to_list() 32 | 33 | 34 | n_500 = pd.read_csv(path + "NIFTY-500.csv") 35 | #n_500.drop(n_500.index[0], axis = 0, inplace=True) 36 | syms_500_nse = n_500["SYMBOL \n"].to_list() 37 | 38 | 39 | 40 | 41 | 42 | for sym in syms_50_nse: 43 | if sym in nifty50: 44 | pass 45 | else: 46 | print(sym, "is not there in NSEpy") 47 | 48 | 49 | for sym in syms_100_nse: 50 | if sym in nifty100: 51 | pass 52 | else: 53 | print(sym, "is not there in NSEpy") 54 | 55 | 56 | for sym in syms_500_nse: 57 | if sym in nifty500: 58 | pass 59 | else: 60 | print(sym, "is not there in NSEpy") 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /python/program.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pandas as pd 3 | from ks_API import KSTrade_API 4 | import matplotlib.pyplot as plt 5 | from datetime import datetime as dt 6 | #from backtesting import BackTestingEngine 7 | from trading_strategy import TradingStrategy 8 | from nsepy.symbols import get_index_constituents_list 9 | 10 | 11 | def Main(broker_API, tickers): 12 | strategy = {} 13 | OHLCV_NSE = {} 14 | Analysis_NSE = TradingStrategy() 15 | Analysis_NSE.Exchange = "NSE" 16 | Analysis_NSE.Dataset = "Close" 17 | for ticker in tickers: 18 | df = Analysis_NSE.HistoricData(ticker, interval = "5m") 19 | OHLCV_NSE[ticker] = df[["Open", "High", "Low", "Close", "Volume"]].copy() 20 | for ticker in OHLCV_NSE: 21 | strategy[ticker] = Analysis_NSE.MA_Crossover(OHLCV_NSE[ticker], MA_type = "HMA") 22 | broker_API.TickersData = strategy 23 | orders = broker_API.Run() 24 | return 25 | 26 | 27 | 28 | if __name__ == "__main__": 29 | Nifty50 = get_index_constituents_list(index = "Nifty50")["Symbol"].to_list() 30 | Nifty100 = get_index_constituents_list(index = "Nifty100")["Symbol"].to_list() 31 | Broker_API = KSTrade_API() 32 | start = time.time() 33 | timeout = time.time() + 60*11 34 | print("waiting...!") 35 | print(Nifty50) 36 | while time.time() <= timeout: 37 | try: 38 | if (dt.now().second == 0) and (dt.now().minute % 5 == 0): 39 | print(dt.now()) 40 | st_time = time.time() 41 | Trade = Main(Broker_API, Nifty50) 42 | ed_time = time.time() 43 | time.sleep((60*5) - (ed_time - st_time + 2)) 44 | print(dt.now()) 45 | except Exception as ex: 46 | print(ex) 47 | break 48 | 49 | Broker_API.Client.logout() 50 | 51 | -------------------------------------------------------------------------------- /python/program_test.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | #import pynse 3 | import datetime as dt 4 | from ks_API import KSTrade_API 5 | import matplotlib.pyplot as plt 6 | from backtesting import BackTestingEngine 7 | from trading_strategy import TradingStrategy 8 | 9 | 10 | 11 | if __name__ == "__main__": 12 | st = dt.datetime.now().timestamp() 13 | print(dt.datetime.now()) 14 | 15 | #pyNSE = pynse.Nse() 16 | #pyNSE_tickers = pyNSE.symbols 17 | #close_df = pd.DataFrame() 18 | IND_tickers = ["ITC", "TRIDENT", "BRITANNIA", "MARUTI", "MRF", "ADANIPORTS", "EICHERMOT", "TITAN", "TATAMOTORS", "ONGC", "NTPC", "LT", "TATASTEEL", "WIPRO", "SBIN", "AXISBANK", "ICICIBANK", "BPCL", "HDFCBANK", "IOC"] 19 | #Nifty50 = pyNSE_tickers["Nifty50"] 20 | #Nifty100 = pyNSE_tickers["Nifty100"] 21 | 22 | #print(Nifty50) 23 | Nifty50 = ["TATAMOTORS"] 24 | 25 | OHLCV_NSE = {} 26 | 27 | start_date = dt.date(2011,1,1) 28 | end_date = dt.date.today() 29 | 30 | Analysis_NSE = TradingStrategy() 31 | 32 | Analysis_NSE.Exchange = "NSE" 33 | Analysis_NSE.Dataset = "Close" 34 | 35 | for ticker in IND_tickers: 36 | df = Analysis_NSE.HistoricData(ticker) # default gives 1d ohlcv data for 5y 37 | #df = Analysis_NSE.HistoricData(ticker, interval = "15m") 38 | OHLCV_NSE[ticker] = df[["Open", "High", "Low", "Close", "Volume"]].copy() 39 | 40 | ed = dt.datetime.now().timestamp() 41 | 42 | #print(OHLCV_NSE) 43 | 44 | indicators = {} 45 | strategies = {} 46 | 47 | for ticker in OHLCV_NSE: 48 | #indicators[ticker]["MACD"] = Analysis_NSE.MACD(OHLCV_NSE[ticker]) 49 | #indicators[ticker]["BBANDS"] = Analysis_NSE.BollingerBand(OHLCV_NSE[ticker]) 50 | strategies[ticker] = Analysis_NSE.MA_Crossover(OHLCV_NSE[ticker], MA_type = "HMA") 51 | #strategies[ticker] = Analysis_NSE.nPeriod_RSI(OHLCV_NSE[ticker]) 52 | #strategies[ticker] = Analysis_NSE.nPeriod_RSI(OHLCV_NSE[ticker]) 53 | 54 | #print(strategies) 55 | #print(strategies["TATAMOTORS"][["long", "short"]].tail(60)) 56 | #print(strategies["TATAMOTORS"][["long", "short"]].diff().tail(60)) 57 | #exit() 58 | 59 | trade_API = KSTrade_API() 60 | trade_API.TickersData = strategies 61 | #c = trade_API.WatchlistName_req() 62 | #o = trade_API.Buy_order(1407) 63 | #c = trade_API.TStop_loss() 64 | #print(o,c) 65 | s = trade_API.Scripmaster_req() 66 | print(s["ITC"]) 67 | #quo = trade_API.Client.quote(instrument_token = 1407) 68 | #print(quo["success"][0]["stk_name"]) 69 | #d = trade_API.Watchlist_req("TradeAPI") 70 | #balance = trade_API.Margin_req() 71 | #print(balance) 72 | #a = trade_API.OrderExecution() 73 | #print(a) 74 | 75 | #print(ed-st) 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /python/requirments.txt: -------------------------------------------------------------------------------- 1 | absl-py==1.3.0 2 | appdirs==1.4.4 3 | astunparse==1.6.3 4 | beautifulsoup4==4.11.1 5 | cachetools==5.2.1 6 | certifi==2022.12.7 7 | cffi==1.15.1 8 | charset-normalizer==2.1.1 9 | click==8.1.3 10 | cryptography==39.0.0 11 | cycler==0.11.0 12 | flatbuffers==23.1.4 13 | fonttools==4.38.0 14 | frozendict==2.3.4 15 | gast==0.4.0 16 | google-auth==2.15.0 17 | google-auth-oauthlib==0.4.6 18 | google-pasta==0.2.0 19 | grpcio==1.51.1 20 | h5py==3.7.0 21 | html5lib==1.1 22 | idna==3.4 23 | importlib-metadata==6.0.0 24 | joblib==1.2.0 25 | keras==2.11.0 26 | kiwisolver==1.4.4 27 | ks-api-client @ git+https://github.com/osparamatrix/ks-orderapi-python.git@6e7a516f8a029393b2cbf0fec7cfeeeefae84cc3 28 | libclang==15.0.6.1 29 | lxml==4.9.2 30 | Markdown==3.4.1 31 | MarkupSafe==2.1.1 32 | matplotlib==3.5.3 33 | multitasking==0.0.11 34 | nsepy==0.8 35 | numpy==1.21.6 36 | oauthlib==3.2.2 37 | opt-einsum==3.3.0 38 | packaging==23.0 39 | pandas==1.3.5 40 | patsy==0.5.3 41 | Pillow==9.4.0 42 | protobuf==3.19.6 43 | pyasn1==0.4.8 44 | pyasn1-modules==0.2.8 45 | pycparser==2.21 46 | pyparsing==3.0.9 47 | python-dateutil==2.8.2 48 | pyti==0.2.2 49 | pytz==2022.7 50 | requests==2.28.1 51 | requests-oauthlib==1.3.1 52 | rsa==4.9 53 | scikit-learn==1.0.2 54 | scipy==1.7.3 55 | six==1.16.0 56 | soupsieve==2.3.2.post1 57 | statsmodels==0.13.5 58 | tensorboard==2.11.0 59 | tensorboard-data-server==0.6.1 60 | tensorboard-plugin-wit==1.8.1 61 | tensorflow==2.11.0 62 | tensorflow-estimator==2.11.0 63 | tensorflow-io-gcs-filesystem==0.29.0 64 | termcolor==2.2.0 65 | threadpoolctl==3.1.0 66 | typing_extensions==4.4.0 67 | urllib3==1.26.13 68 | webencodings==0.5.1 69 | Werkzeug==2.2.2 70 | wrapt==1.14.1 71 | yfinance==0.2.3 72 | zipp==3.11.0 73 | -------------------------------------------------------------------------------- /python/technical_analysis.py: -------------------------------------------------------------------------------- 1 | #import talib 2 | import numpy as np 3 | import pandas as pd 4 | import yfinance as yf 5 | import statsmodels.api as sm 6 | import matplotlib.pyplot as plt 7 | from pyti.hull_moving_average import hull_moving_average as hma 8 | from pyti.weighted_moving_average import weighted_moving_average as wma 9 | 10 | 11 | class TechnicalAnalysis(object): 12 | def __init__(self): 13 | self.exchange = None 14 | self.dataset = None 15 | return 16 | 17 | @property 18 | def Exchange(self): 19 | if self.exchange: 20 | return self.exchange 21 | else: 22 | raise ValueError("Declare the Exchange") 23 | 24 | @Exchange.setter 25 | def Exchange(self, EX): 26 | self.exchange = EX 27 | 28 | @property 29 | def Ticker_ext(self): 30 | ext = "" 31 | if self.exchange == "NSE": 32 | ext = ".NS" 33 | elif self.exchange == "BSE": 34 | ext = ".BS" 35 | elif self.exchange == None: 36 | raise ValueError("Declare the Exchange") 37 | self.yf_ticker_ext = ext 38 | return self.yf_ticker_ext 39 | 40 | @property 41 | def Dataset(self): 42 | if self.dataset: 43 | return self.dataset 44 | else: 45 | raise ValueError("Declare the Dataset") 46 | 47 | @Dataset.setter 48 | def Dataset(self, Var): 49 | self.dataset = Var 50 | 51 | 52 | def HistoricData(self, ticker, interval = "1d", threads = None, start = None, end = None): 53 | self.yfTicker = yf.Ticker(ticker + self.Ticker_ext) 54 | period = None 55 | if start == None and end == None: 56 | if interval in ["1m"]: 57 | period = "7d" 58 | elif interval in ["2m", "5m", "15m", "30m", "90m"]: 59 | period = "60d" 60 | elif interval in ["60m", "1hr"]: 61 | period = "730d" 62 | elif interval in ["1d", "5d", "1wk", "1mo", "3mo"] and period == None: 63 | period = "5y" 64 | #historic_data = self.yfTicker.history(period = period, interval = interval, threads = threads) 65 | historic_data = self.yfTicker.history(period = period, interval = interval) 66 | else: 67 | #historic_data = self.yfTicker.history(start = start, end = end, threads = threads) # WARNING history() got an unexpected keyword argument 'threads' 68 | historic_data = self.yfTicker.history(start = start, end = end) 69 | historic_data.fillna(method = "bfill", inplace = True) 70 | #historic_data.drop(historic_data.tail(1).index,inplace=True) 71 | return historic_data 72 | 73 | 74 | def MovingAvg(self, Data, n=9, MA_type = "SMA"): 75 | df = Data.copy() 76 | DataSet = self.Dataset 77 | if MA_type == "SMA": 78 | df[f"{MA_type}"] = df[DataSet].rolling(window = n).mean() 79 | elif MA_type == "EMA": 80 | df[f"{MA_type}"] = df[DataSet].ewm(span=n, min_periods=n).mean() 81 | elif MA_type == "WMA": 82 | df[f"{MA_type}"] = wma(df[DataSet], n) 83 | elif MA_type == "HMA": 84 | df[f"{MA_type}"] = hma(df[DataSet], n) 85 | else: 86 | print("Enter a valid Moving average type") 87 | raise(TypeError) 88 | return df[[DataSet, f"{MA_type}"]] 89 | 90 | 91 | def MACD_Crossover(self, Data, a=12 ,b=26, c=9, signal_type = "SLC", MA_type = "EMA"): 92 | """function to calculate MACD 93 | typical values a(fast moving average) = 12; 94 | b(slow moving average) =26; 95 | c(signal line ma window) =9; 96 | SLC (Signal Line Crossover) 97 | Bullish Signal Line Crossovers occur when the MACD Line crosses above the Signal Line. 98 | Bearish Signal Line Crossovers occur when the MACD Line crosses below the Signal Line. 99 | ZLC (Zero Line Crossover) 100 | Bullish Zero Line Crossovers occur when the MACD Line crosses above the Zero Line and go from negative to positive. 101 | Bearish Zero Line Crossovers occur when the MACD Line crosses below the Zero Line and go from positive to negative. 102 | """ 103 | df = Data.copy() 104 | DataSet = self.Dataset 105 | MA_fast = self.MovingAvg(df, n = a, MA_type = MA_type) 106 | MA_slow = self.MovingAvg(df, n = b, MA_type = MA_type) 107 | df["ma_fast"] = MA_fast[f"{MA_type}"] 108 | df["ma_slow"] = MA_slow[f"{MA_type}"] 109 | df["macd"] = df["ma_fast"] - df["ma_slow"] 110 | df["signal_line"] = df["macd"].ewm(span=c, min_periods=c).mean() 111 | df["signal"] = 0.0 112 | if signal_type == "SLC": 113 | df["signal"] = np.where(df["macd"] > df["signal_line"], 1.0, 0.0) 114 | elif signal_type == "ZLC": 115 | df["signal"] = np.where(df["macd"] > 0, 1.0, 0.0) 116 | df["%s_signal" %signal_type] = df["signal"].diff() 117 | for i in df["%s_signal" %signal_type]: 118 | if i==1.0: 119 | print("BUY") 120 | elif i==-1.0: 121 | print("SELL") 122 | return df[[DataSet, "macd", "signal_line", "%s_signal" %signal_type]] 123 | 124 | 125 | def RSI(self, Data, n=14, UL = 70, LL = 30): 126 | """ RSI (RSI value range is 0-100) 127 | Any number above 70 should be considered overbought (OB). 128 | Any number below 30 should be considered oversold (OS). 129 | """ 130 | df = Data.copy() 131 | DataSet = self.Dataset 132 | df["change"] = df[DataSet] - df[DataSet].shift(1) 133 | df["gain"] = np.where(df["change"]>=0, df["change"], 0) 134 | df["loss"] = np.where(df["change"]<0, -1*df["change"], 0) 135 | df["avgGain"] = df["gain"].ewm(span=n, min_periods=n).mean() 136 | df["avgLoss"] = df["loss"].ewm(span=n, min_periods=n).mean() 137 | df["rs"] = df["avgGain"]/df["avgLoss"] 138 | df["rsi"] = 100 - (100/ (1 + df["rs"])) 139 | df["signal"] = np.where(df["rsi"] <= LL, "OS", (np.where(df["rsi"] >= UL, "OB", "N"))) 140 | return df[[DataSet, "rsi", "signal"]] 141 | 142 | 143 | def BollingerBand(self, Data, n=20, s=2): 144 | """ Used to measure the volatality in conjunction with ATR 145 | When prices move up near the upper band or even break through the upper band, that security may be seen as overbought. 146 | When prices move down near the lower band or even break through the lower band, that security may be seen as oversold. 147 | """ 148 | df = Data.copy() 149 | DataSet = self.Dataset 150 | df["MB"] = df[DataSet].rolling(window = n).mean() 151 | df["UB"] = df["MB"] + s*df[DataSet].rolling(window = n).std(ddof=0) 152 | df["LB"] = df["MB"] - s*df[DataSet].rolling(window = n).std(ddof=0) 153 | df["BB_Width"] = df["UB"] - df["LB"] 154 | df["signal"] = np.where(df[DataSet] > df["UB"], "OB", (np.where(df[DataSet] < df["LB"], "OS", "N"))) 155 | return df[[DataSet, "MB", "UB", "LB", "BB_Width", "signal"]] 156 | 157 | 158 | def ATR(self, Data, n=14): 159 | """ Average True Range which can used to measure the volatality in conjunction with Bollinger Band 160 | """ 161 | df = Data.copy() 162 | DataSet = self.Dataset 163 | df["H-L"] = df["High"] - df["Low"] 164 | df["H-PC"] = abs(df["High"] - df[DataSet].shift(1)) 165 | df["L-PC"] = abs(df["Low"] - df[DataSet].shift(1)) 166 | df["TR"] = df[["H-L","H-PC","L-PC"]].max(axis=1, skipna=False) 167 | df["ATR"] = df["TR"].ewm(span=n, min_periods=n).mean() 168 | return df["ATR"] 169 | 170 | 171 | def ADX(self, Data, n=14, UL = 25, LL = 20, DI_Crossover = False): 172 | """ Trend Strength (TS) 173 | To analyze trend strength, the focus should be on the ADX line and not the +DI or -DI lines. 174 | ADX reading above 25 indicated a strong trend (ST), while a reading below 20 indicated a weak or non-existent trend (WT). 175 | A reading between those two values, would be considered indeterminable (NA). 176 | DI Crossover (DIC) 177 | Bullish DI Crossover: 178 | ADX must be over 25 (strong trend). 179 | The +DI crosses above the -DI. 180 | Bearish DI Crossover: 181 | ADX must be over 25 (strong trend). 182 | The +DI crosses below the -DI. 183 | """ 184 | df = Data.copy() 185 | DataSet = self.Dataset 186 | df["ATR"] = self.ATR(df, n) 187 | df["upmove"] = df["High"] - df["High"].shift(1) 188 | df["downmove"] = df["Low"].shift(1) - df["Low"] 189 | df["+dm"] = np.where((df["upmove"]>df["downmove"]) & (df["upmove"] >0), df["upmove"], 0) 190 | df["-dm"] = np.where((df["downmove"]>df["upmove"]) & (df["downmove"] >0), df["downmove"], 0) 191 | df["+di"] = 100 * (df["+dm"]/df["ATR"]).ewm(span=n, min_periods=n).mean() 192 | df["-di"] = 100 * (df["-dm"]/df["ATR"]).ewm(span=n, min_periods=n).mean() 193 | df["ADX"] = 100 * abs((df["+di"] - df["-di"])/(df["+di"] + df["-di"])).ewm(span=n, min_periods=n).mean() 194 | df["TS"] = np.where(df["ADX"] > UL, "ST", (np.where(df["ADX"] < LL, "WT", "NA"))) 195 | df["DI_signal"] = 0.0 196 | if DI_Crossover: 197 | df["DI_signal"] = np.where(df["ADX"] > UL, (np.where(df["+di"] > df["-di"], 1.0, 0.0)), 0.0) 198 | df["DI_signal"] = df["DI_signal"].diff() 199 | return df[[DataSet, "ATR", "+di", "-di", "ADX", "TS", "DI_signal"]] 200 | 201 | 202 | def CMF(self, Data, n=20): 203 | """ Chaikin Money Flow (CMF) is a technical analysis indicator used to measure Money Flow Volume 204 | CMF_Crossover 205 | Bullish Crosses occur when Chaikin Money Flow crosses from below the Zero Line to above the Zero Line. Price then rises. 206 | Bearish Crosses occur when Chaikin Money Flow crosses from above the Zero Line to below the Zero Line. Price then falls. 207 | We can adjust value of zero line to reduce false signals. (ex: 0.05 <--> -0.05 ) 208 | """ 209 | df = Data.copy() 210 | DataSet = self.Dataset 211 | df["multiplier"] = (2*df[DataSet] - df["High"] - df["Low"])/(df["High"] - df["Low"]) 212 | df["MFV"] = df["multiplier"] * df["Volume"] 213 | df["CMF"] = df["MFV"].rolling(window = n).sum()/df["Volume"].rolling(window = n).sum() 214 | df["signal"] = np.where(df["CMF"] > 0.05, 1.0, 0.0) 215 | df["signal"] = df["signal"].diff() 216 | #print(df.tail(50)) 217 | return df[[DataSet, "CMF", "signal"]] 218 | 219 | 220 | def Slope(self, series, span = 50): 221 | """Calculates the slope of regression line for n(span) consecutive points on a plot""" 222 | ser = (series - series.min())/(series.max() - series.min()) 223 | x = np.array(range(len(ser))) 224 | x = (x - x.min())/(x.max() - x.min()) 225 | slopes = [i*0 for i in range(span-1)] 226 | for i in range(span, len(ser)+1): 227 | y_scaled = ser[i-span:i] 228 | x_scaled = x[:span] 229 | x_scaled = sm.add_constant(x_scaled) 230 | model = sm.OLS(y_scaled, x_scaled) 231 | results = model.fit() 232 | slopes += [results.params[-1]] 233 | slope_angle = (np.rad2deg(np.arctan(np.array(slopes)))) 234 | return np.array(slope_angle) 235 | 236 | 237 | def APO(self, Data, a = 20, b = 60, MA_type = "EMA"): 238 | """ 239 | """ 240 | df = Data.copy() 241 | DataSet = self.Dataset 242 | MA_fast = self.MovingAvg(df, n = a, MA_type = MA_type) 243 | MA_slow = self.MovingAvg(df, n = b, MA_type = MA_type) 244 | df["ma_fast"] = MA_fast[f"{MA_type}"] 245 | df["ma_slow"] = MA_slow[f"{MA_type}"] 246 | df["apo"] = df["ma_fast"] - df["ma_slow"] 247 | df["signal"] = 0.0 #WARNING should add 248 | return df[[DataSet, "ma_slow", "ma_fast", "apo"]] 249 | 250 | 251 | 252 | 253 | # ---------------------------------------------------------------------------- # 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /python/test.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import yfinance as yf 3 | from LSTM import LSTM_DL 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | ticker = yf.Ticker("MRF.NS") 8 | data = ticker.history(start = "2022-04-01", end = "2022-05-25", interval = "5m") 9 | #print(data) 10 | 11 | 12 | lstm = LSTM_DL("MRF", data, time_step = 75, epochs=100) 13 | model = lstm.RNN 14 | a = lstm.Prediction(model, 75) 15 | 16 | 17 | daterange = pd.date_range(start ='2022-05-25 09:15', freq ='5min', periods = 75, tz ='Asia/Calcutta') 18 | new = pd.DataFrame({"Pred_Close" : a}, index = pd.DatetimeIndex(daterange)) 19 | 20 | 21 | 22 | print(data) 23 | print(new) 24 | 25 | plt.plot(data["Close"], label="closing") 26 | plt.plot(new["Pred_Close"], label="nextday_prediction") 27 | plt.legend() 28 | plt.show() 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /python/trading_platform.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | from collections import deque 4 | from technical_analysis import TechnicalAnalysis 5 | 6 | 7 | class TradingPlatform(object): 8 | def __init__(self): 9 | self.Data = None 10 | self.Tickers = list() 11 | self.long_dataset = "long" 12 | self.short_dataset = "short" 13 | self.TickerPosition = dict() 14 | self.x = deque(maxlen = 10) # 15 | self.position = None 16 | self.order_pending = None 17 | return 18 | 19 | 20 | @property 21 | def TickersData(self): 22 | if self.Data: 23 | return self.Data 24 | else: 25 | raise ValueError("Set the TickersData") 26 | 27 | 28 | @TickersData.setter 29 | def TickersData(self, data): 30 | self.Data = data 31 | 32 | 33 | @property 34 | def Get_Tickers(self): 35 | data = self.TickersData 36 | for ticker in data: 37 | self.Tickers += [ticker] 38 | return self.Tickers 39 | 40 | 41 | def SignalParser(self, Data): 42 | self.is_long = None 43 | self.is_short = None 44 | self.signal_parsed = None 45 | try: 46 | if Data[self.long_dataset][-1] == 1.0: 47 | self.is_long = True 48 | delattr(self, "is_short") 49 | elif Data[self.short_dataset][-1] == 1.0: 50 | self.is_short = True 51 | delattr(self, "is_long") 52 | else: 53 | delattr(self, "is_long") 54 | delattr(self, "is_short") 55 | self.signal_parsed = True 56 | except Exception as ex: 57 | print("SignalParser error:", type(ex).__name__, ex) 58 | print("Please provide the Strategy implemented Data") 59 | pass 60 | return self.signal_parsed 61 | 62 | 63 | def PositionType(self, Data): 64 | signal = self.SignalParser(Data) 65 | if signal: 66 | if hasattr(self, "is_long") and self.is_long: 67 | self.position = "BUY" 68 | elif hasattr(self, "is_short") and self.is_short: 69 | self.position = "SELL" 70 | else: 71 | self.position = None 72 | else: 73 | print("Signal is not parsed. Check SignalParser") 74 | return self.position 75 | 76 | 77 | @property 78 | def Get_TickerPosition(self): 79 | data = self.TickersData 80 | for ticker in data: 81 | position = self.PositionType(data[ticker]) 82 | price = data[ticker]["Close"][-1] 83 | self.TickerPosition[ticker] = [position, price] 84 | return self.TickerPosition 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /python/trading_strategy.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | from technical_analysis import TechnicalAnalysis 4 | 5 | 6 | class TradingStrategy(TechnicalAnalysis): 7 | def __init__(self): 8 | # initiate TechnicalAnalysis class 9 | super(TradingStrategy, self).__init__() 10 | return 11 | 12 | 13 | def MA_Crossover(self, Data, short_span = 20, long_span = 60, MA_type = "SMA", signal_type = "PMAC"): 14 | """ 15 | MAC (Moving Avg Crossover) 16 | Bullish Crossover: Occurs when the short-term SMA crosses above the long-term SMA. Also known as Golden cross. 17 | Bearish Crossover: Occurs when the short-term SMA crosses below the long-term SMA. Also known as Dead cross. 18 | PMAC (Price and Moving Avg Crossover) 19 | Bullish Price Crossover: Price crosses above short-term SMA while the short-term SMA is above the long-term SMA. 20 | Bearish Price Crossover: Price crosses below short-term SMA while the short-term SMA is below the long-term SMA. 21 | """ 22 | df = Data.copy() 23 | DataSet = self.Dataset 24 | Strat_Data = pd.DataFrame(index = df.index) 25 | MA_slow = self.MovingAvg(df, n = long_span, MA_type = MA_type) 26 | MA_fast = self.MovingAvg(df, n = short_span, MA_type = MA_type) 27 | Strat_Data[DataSet] = df[DataSet] 28 | Strat_Data["ma_slow"] = MA_slow[f"{MA_type}"] 29 | Strat_Data["ma_fast"] = MA_fast[f"{MA_type}"] 30 | 31 | #RSI = self.RSI(df, n = 2, UL = 90, LL = 10) # WARNING TEST 32 | #Strat_Data["rsi"] = RSI["rsi"] 33 | if signal_type == "MAC": 34 | Strat_Data["long"] = np.where(Strat_Data["ma_fast"] > Strat_Data["ma_slow"], 1.0, 0.0) 35 | Strat_Data["short"] = np.where(Strat_Data["ma_fast"] < Strat_Data["ma_slow"], 1.0, 0.0) 36 | elif signal_type == "PMAC": 37 | Strat_Data["long"] = np.where(Strat_Data[DataSet] > Strat_Data["ma_slow"], (np.where(Strat_Data["ma_fast"] > Strat_Data["ma_slow"], 1.0, 0.0)), 0.0) 38 | Strat_Data["short"] = np.where(Strat_Data[DataSet] < Strat_Data["ma_slow"], 1.0, 0.0) 39 | Strat_Data["long"] = Strat_Data["long"].diff() 40 | Strat_Data["short"] = Strat_Data["short"].diff() 41 | return Strat_Data 42 | 43 | 44 | def nPeriod_RSI(self, Data, span = 2, short_span = 5, long_span = 60, MA_type = "SMA"): 45 | """ 46 | 1. The price is above its 200-day moving average. 47 | 2. The 2-period RSI of the price closes below 5. 48 | 3. Buy the EQ on the close. 49 | 4. Exit when the price closes above its 5-period moving average. 50 | """ 51 | df = Data.copy() 52 | DataSet = self.Dataset 53 | Strat_Data = pd.DataFrame(index = df.index) 54 | RSI = self.RSI(df, n = span, UL = 90, LL = 10) 55 | MA_slow = self.MovingAvg(df, n = long_span, MA_type = MA_type) 56 | MA_fast = self.MovingAvg(df, n = short_span, MA_type = MA_type) 57 | Strat_Data[DataSet] = df[DataSet] 58 | Strat_Data["ma_slow"] = MA_slow[f"{MA_type}"] 59 | Strat_Data["ma_fast"] = MA_fast[f"{MA_type}"] 60 | Strat_Data["rsi"] = RSI["rsi"] 61 | Strat_Data["long"] = np.where(Strat_Data[DataSet] > Strat_Data["ma_slow"], (np.where(RSI["signal"] == "OS", 1.0, 0.0)), 0.0) 62 | Strat_Data["short"] = np.where(RSI["signal"] == "OB", 1.0, 0.0) 63 | Strat_Data["long"] = Strat_Data["long"].diff() 64 | Strat_Data["short"] = Strat_Data["short"].diff() 65 | return Strat_Data 66 | 67 | 68 | def Cumulative_RSI(self, Data, X = 2, Y = 40, span = 2, short_span = 5, long_span = 60, MA_type = "SMA"): 69 | """ 70 | 1. The security being used is above its 200-day moving average. 71 | 2. Use a 2-Period RSI. 72 | 3. Take the past X days of the 2-period RSI and add them up. 73 | 4. Buy if the Cumulative RSI is below Y (I'll share with you results using different numbers for X and Y). 74 | 5. Exit when the 2-period RSI closes above 65 (you can also exit using any of the exit strategies taught later in the Exits chapter). 75 | """ 76 | df = Data.copy() 77 | DataSet = self.Dataset 78 | Strat_Data = pd.DataFrame(index = df.index) 79 | nRSI = self.nPeriod_RSI(df, span = span, short_span = short_span, long_span = long_span, MA_type = MA_type) 80 | Strat_Data[DataSet] = nRSI[DataSet] 81 | Strat_Data["ma_slow"] = nRSI["ma_slow"] 82 | Strat_Data["ma_fast"] = nRSI["ma_fast"] 83 | Strat_Data["rsi"] = nRSI["rsi"] 84 | Strat_Data["crsi"] = nRSI["rsi"].rolling(window = X).sum() 85 | Strat_Data["long"] = np.where(Strat_Data[DataSet] > Strat_Data["ma_slow"], (np.where(Strat_Data["crsi"] < Y, 1.0, 0.0)), 0.0) 86 | Strat_Data["short"] = np.where(Strat_Data["crsi"] > 60, 1.0, 0.0) 87 | Strat_Data["long"] = Strat_Data["long"].diff() 88 | Strat_Data["short"] = Strat_Data["short"].diff() 89 | return Strat_Data 90 | 91 | 92 | def Double_N(self, Data, N = 7, long_span = 60, MA_type = "SMA"): 93 | """ 94 | 1. The security is above its 200-day moving average 95 | 2. If the security closes at a 7-day low, buy. 96 | 3. If the security closes at a 7-day high, sell your long position. 97 | """ 98 | df = Data.copy() 99 | DataSet = self.Dataset 100 | Strat_Data = pd.DataFrame(index = df.index) 101 | MA_slow = self.MovingAvg(df, n = long_span, MA_type = MA_type) 102 | Strat_Data[DataSet] = df[DataSet] 103 | Strat_Data["ma_slow"] = MA_slow[f"{MA_type}"] 104 | Strat_Data["N_max"] = Strat_Data[DataSet].rolling(window = N).max() 105 | Strat_Data["N_min"] = Strat_Data[DataSet].rolling(window = N).min() 106 | Strat_Data["long"] = np.where(Strat_Data[DataSet] > Strat_Data["ma_slow"], (np.where(Strat_Data[DataSet] == Strat_Data["N_min"], 1.0, 0.0)), 0.0) 107 | Strat_Data["short"] = np.where(Strat_Data[DataSet] == Strat_Data["N_max"], 1.0, 0.0) 108 | Strat_Data["long"] = Strat_Data["long"].diff() 109 | Strat_Data["short"] = Strat_Data["short"].diff() 110 | return Strat_Data 111 | 112 | 113 | def SupportResistance(self, Data, N = 10, Tolerance_pct = 20, Tolerance_limit = 3): 114 | """ 115 | 1. The level of support and resistance is calculated by taking the maximum and minimum price and then subtracting and adding a 20% margin (Tolerance_pct). 116 | 2. We will buy when we reach the support line, and sell when we reach the resistance line. 117 | """ 118 | df = Data.copy() 119 | DataSet = self.Dataset 120 | Strat_Data = pd.DataFrame(index = df.index) 121 | Strat_Data[DataSet] = df[DataSet] 122 | Strat_Data["sup"] = Strat_Data[DataSet].rolling(window = N).min() 123 | Strat_Data["res"] = Strat_Data[DataSet].rolling(window = N).max() 124 | Strat_Data["sup_tolerance"] = Strat_Data["sup"] + (Tolerance_pct/100)*(abs(Strat_Data["sup"] - Strat_Data["res"])) 125 | Strat_Data["res_tolerance"] = Strat_Data["res"] - (Tolerance_pct/100)*(abs(Strat_Data["sup"] - Strat_Data["res"])) 126 | Strat_Data["in_resistance"] = np.where(Strat_Data[DataSet] >= Strat_Data["res_tolerance"], (np.where(Strat_Data[DataSet] <= Strat_Data["res"], 1.0, 0.0)), 0.0) 127 | Strat_Data["in_support"] = np.where(Strat_Data[DataSet] <= Strat_Data["sup_tolerance"], (np.where(Strat_Data[DataSet] >= Strat_Data["sup"], 1.0, 0.0)), 0.0) 128 | Strat_Data["sup_count"] = Strat_Data["in_support"].rolling(window = Tolerance_limit).sum() 129 | Strat_Data["res_count"] = Strat_Data["in_resistance"].rolling(window = Tolerance_limit).sum() 130 | Strat_Data["long"] = np.where(Strat_Data["sup_count"] == Tolerance_limit, 1.0, 0.0) 131 | Strat_Data["short"] = np.where(Strat_Data["res_count"] == Tolerance_limit, 1.0, 0.0) 132 | Strat_Data["long"] = Strat_Data["long"].diff() 133 | Strat_Data["short"] = Strat_Data["short"].diff() 134 | return Strat_Data 135 | 136 | 137 | def MeanReversion(self, Data, N = 20, short_span = 20, long_span = 60, threshold = 10, MA_type = "EMA"): 138 | """ 139 | 1. The APO trading signal value is above Sell-Entry threshold and the difference between last trade-price and current-price is different enough. 140 | 2. We are long( +ve position ) and either APO trading signal value is at or above 0 or current position is profitable enough to lock profit. 141 | 3. The APO trading signal value is below Buy-Entry threshold and the difference between last trade-price and current-price is different enough. 142 | 4. We are short( -ve position ) and either APO trading signal value is at or below 0 or current position is profitable enough to lock profit. 143 | """ 144 | df = Data.copy() 145 | DataSet = self.Dataset 146 | Strat_Data = pd.DataFrame(index = df.index) 147 | apo = self.APO(df, a = short_span, b = long_span, MA_type = MA_type) 148 | Strat_Data[DataSet] = df[DataSet] 149 | Strat_Data["apo"] = apo["apo"] 150 | Strat_Data["std"] = Strat_Data[DataSet].rolling(window = N).std(ddof=0) 151 | Strat_Data["std_factor"] = Strat_Data["std"]/Strat_Data["std"].rolling(window = N).mean() 152 | Strat_Data["long"] = np.where(Strat_Data["apo"] < (-threshold), 1.0, 0.0) # WARNING doubt 153 | Strat_Data["short"] = np.where(Strat_Data["apo"] > threshold, 1.0, 0.0) # WARNING doubt 154 | Strat_Data["long"] = Strat_Data["long"].diff() 155 | Strat_Data["short"] = Strat_Data["short"].diff() 156 | return Strat_Data 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /python/vbt_test.py: -------------------------------------------------------------------------------- 1 | import vectorbt as vbt 2 | import numpy as np 3 | 4 | btc_price_1d = vbt.YFData.download("BTC-USD", missing_index='drop', interval="1D").get('Close') 5 | 6 | 7 | class MA(): 8 | def __init__(self): 9 | return 10 | 11 | def ma_strategy(self,close, window = 730, lower_multiplier=1, upper_multiplier=4): 12 | signal = np.full(close.shape, np.nan) 13 | for x in range(len(close)): 14 | if x >= window: 15 | mavg = np.mean( close[x-window:x]) 16 | if close[x] < mavg*lower_multiplier: 17 | signal[x] = 1 18 | elif close[x] > mavg*upper_multiplier: 19 | signal[x] = -1 20 | else: 21 | signal[x] = 0 22 | 23 | return signal 24 | 25 | ma = MA() 26 | 27 | my_indicator = vbt.IndicatorFactory( 28 | class_name="ma_strategy", 29 | short_name="ma", 30 | input_names=["close"], 31 | param_names=["window","lower_multiplier","upper_multiplier"], 32 | output_names=["signal"] 33 | ).from_apply_func( 34 | ma.ma_strategy, 35 | window=730, 36 | lower_multiplier=1, 37 | upper_multiplier=4 38 | ) 39 | 40 | results = my_indicator.run(btc_price_1d) 41 | print(results.signal) 42 | --------------------------------------------------------------------------------