|
| 1 | +""" |
| 2 | +Trading system as a script. |
| 3 | +See the instructions in the notebook on how this works. |
| 4 | +The only change here is that the bhav file is |
| 5 | +automatically picked up from the web. You need to |
| 6 | +change this manually in the utils.py file if there is a holiday. |
| 7 | +""" |
| 8 | + |
| 9 | +# Import libraries |
| 10 | +import pandas as pd |
| 11 | +import os |
| 12 | +import datetime |
| 13 | +import json |
| 14 | +from jinja2 import Environment, FileSystemLoader |
| 15 | +from utils import * |
| 16 | + |
| 17 | +# PARAMETERS |
| 18 | +# Only change the parameters to |
| 19 | + |
| 20 | +UNIVERSE = 'NIFTY50' # Universe to be searched |
| 21 | +STOP_LOSS = 3 # Stop loss for the order |
| 22 | +NUM_STOCKS = 5 # Number of stocks to sell |
| 23 | +CAPITAL = 20000 |
| 24 | +LEVERAGE = 1 |
| 25 | +ORDERFILE_PREFIX = 'orders_' # Prefix file name to store |
| 26 | +DIVERSIFY = False # whether to diversify data among sectors |
| 27 | +ACCOUNTID = 'XXXXXX' # For any user |
| 28 | +APIKEY = 'xxxxxxxxxxxxxxxx' # For zerodha |
| 29 | + |
| 30 | +# Prepare the dataframe |
| 31 | +yesterday, today = pd.bdate_range(end=datetime.datetime.now(), periods=2) |
| 32 | +preopen = fetch_preopen_data() |
| 33 | +eod = get_bhav_copy(yesterday) |
| 34 | +symbols = pd.read_excel('universe.xlsx', sheet_name=UNIVERSE, header=None).values.ravel() |
| 35 | +sectors = pd.read_csv('sectors.csv') |
| 36 | +df = eod[eod['SYMBOL'].isin(symbols)] |
| 37 | +df = df[df['SERIES'] == "EQ"].reset_index(drop=True) |
| 38 | +df = df.merge(sectors) |
| 39 | + |
| 40 | + |
| 41 | +# Diversify function for diversifying sectors |
| 42 | +def diversify(frame, n=5): |
| 43 | + """ |
| 44 | + Diversify stocks among sectors |
| 45 | + frame |
| 46 | + dataframe |
| 47 | + n |
| 48 | + number of stocks |
| 49 | + """ |
| 50 | + industry = set() |
| 51 | + stocks = [] |
| 52 | + for k,v in frame.iterrows(): |
| 53 | + if v.at['Industry'] not in industry: |
| 54 | + industry.add(v.at['Industry']) |
| 55 | + stocks.append(v) |
| 56 | + if len(stocks) == n: |
| 57 | + break |
| 58 | + f = pd.DataFrame(stocks) |
| 59 | + if len(f) == 0: |
| 60 | + return pd.DataFrame() |
| 61 | + else: |
| 62 | + return f |
| 63 | + |
| 64 | +# Trading logic |
| 65 | + |
| 66 | +df['RET'] = (df['CLOSE']/df['PREVCLOSE']) - 1 |
| 67 | +if DIVERSIFY: |
| 68 | + result = diversify(df.sort_values(by='RET', ascending=False), NUM_STOCKS) |
| 69 | +else: |
| 70 | + result = df.sort_values(by='RET', ascending = False).iloc[:NUM_STOCKS] |
| 71 | +trading_capital = CAPITAL * LEVERAGE |
| 72 | +orders = result.merge(preopen, on='SYMBOL') |
| 73 | +num_stock = len(orders) |
| 74 | + |
| 75 | +# Pricing logic goes here |
| 76 | + |
| 77 | +orders['trigger_price'] = (orders['OpenPrice'] - 0.05).round(2) |
| 78 | +orders['price'] = (orders['trigger_price'] - 0.05).round(2) |
| 79 | + |
| 80 | +# I prefer a constant percentage change instead of value |
| 81 | +# If you prefer it, uncomment the below two lines |
| 82 | + |
| 83 | +#orders['trigger_price'] = orders['OpenPrice'] * 0.9985 |
| 84 | +#orders['price'] = orders['trigger_price'] - 0.05 |
| 85 | + |
| 86 | +orders['stop_loss'] = (orders['price'] * (1 + STOP_LOSS * 0.01)).apply(tick).round(2) |
| 87 | +orders['qty'] = (trading_capital/num_stock/orders['price']).astype(int) |
| 88 | +orders['order'] = 'SELL' |
| 89 | +filename = ORDERFILE_PREFIX + datetime.datetime.today().strftime('%Y-%m-%d') + '.csv' |
| 90 | +orders.to_csv('orders/' + filename, index=False) |
| 91 | + |
| 92 | +def generate_nest(): |
| 93 | + """ |
| 94 | + Most brokers and software provide an option to place basket orders. |
| 95 | + So we are going to create a basket order from our orders. We would be using the ``create_order`` function from ``utils.py`` file. |
| 96 | + To do this for your specific broker, do the following steps |
| 97 | + 1. Know the format of your broker; you can do this by placing a basket order and exporting it |
| 98 | + 2. The format would usually have a list of columns to be filled up. We need to fill all the columns to import our order. |
| 99 | + 3. We separate the columns into columns that are already in our dataframe and columns to be included |
| 100 | + 4. We prepare a list of matching columns and rename them |
| 101 | + 5. For new columns, we create a python dictionary with keys as column names and values as the value for the column (we assume that these columns have a single value) |
| 102 | +
|
| 103 | + Thanks @vjay for providing the necessary support |
| 104 | + """ |
| 105 | + # List of columns to be included in the output |
| 106 | + |
| 107 | + cols = [ |
| 108 | + 'Segment', 'InstrumentName', 'Symbol', 'Option Type', 'Strike Price', |
| 109 | + 'ExpiryDate', 'Price', 'Qty', 'LTP', 'Buy/Sell', 'Order Type', |
| 110 | + 'TriggerPrice', 'Pro/Cli', 'P Type', 'Validity', 'AccountId', |
| 111 | + 'Validity Date', 'Remarks', 'Participant code', 'Validity Time', |
| 112 | + 'Market Proc' |
| 113 | + ] |
| 114 | + |
| 115 | + # These columns are common for all orders - columns with a single name |
| 116 | + columns = { |
| 117 | + 'Segment': 'NSE', |
| 118 | + 'InstrumentName': 'EQ', |
| 119 | + 'Option Type': 'NA', |
| 120 | + 'Strike Price': 'NA', |
| 121 | + 'ExpiryDate': 'NA', |
| 122 | + 'LTP': 0, |
| 123 | + 'Disclosed Qty': 0, |
| 124 | + 'AccountId': ACCOUNTID, |
| 125 | + 'Pro/Cli': 'CLI', |
| 126 | + 'Validity': 'DAY', |
| 127 | + 'P Type': 'MIS', |
| 128 | + 'Remarks': '', |
| 129 | + 'Validity Date': 'NA', |
| 130 | + 'Participant code': '', |
| 131 | + 'Validity Time': 'NA', |
| 132 | + 'Market Proc': 'NA', |
| 133 | + 'Order Type': 'SL' |
| 134 | + } |
| 135 | + |
| 136 | + # These are columns to be renamed |
| 137 | + rename = { |
| 138 | + 'order': 'Buy/Sell', |
| 139 | + 'price': 'Price', |
| 140 | + 'qty': 'Qty', |
| 141 | + 'trigger_price': 'TriggerPrice', |
| 142 | + 'price': 'Price' , |
| 143 | + 'SYMBOL': 'Symbol' |
| 144 | + } |
| 145 | + |
| 146 | + |
| 147 | + # Generating orders in the required format |
| 148 | + entry_orders = orders.copy() |
| 149 | + entry = create_orders(entry_orders, rename=rename, **columns) |
| 150 | + |
| 151 | + # Exit orders order type and price to be changed |
| 152 | + exit_orders = orders.copy() |
| 153 | + exit_orders['order'] = 'BUY' |
| 154 | + exit_orders['price'] = 0 |
| 155 | + exit_orders['trigger_price'] = stop_loss(orders['price'], 3, order='S').round(2) |
| 156 | + columns.update({'Order Type': 'SL-M'}) |
| 157 | + exit = create_orders(exit_orders, rename=rename, **columns) |
| 158 | + |
| 159 | + # File generation |
| 160 | + entry.append(exit, sort=False)[cols].to_csv('orders_to_place.csv', |
| 161 | + index=False, header=False) |
| 162 | + |
| 163 | + print('File generated for NEST') |
| 164 | + |
| 165 | +def generate_zerodha(): |
| 166 | + """ |
| 167 | + Order generation for Kite Zerodha |
| 168 | + 1. Sign up for a zerodha publisher api key at(https://kite.trade/) |
| 169 | + 2. Update the API key in parameters |
| 170 | + 3. This would create an html file **zerodha_order.html** in your present working directory. |
| 171 | + 4. Open the HTML file and click the submit button to log into zerodha and place your orders. |
| 172 | + 5. Check the updated at time to make sure that this is the latest generated order. |
| 173 | + """ |
| 174 | + |
| 175 | + # List of columns to be included in the output |
| 176 | + cols = [ |
| 177 | + 'tradingsymbol', 'exchange', 'transaction_type', 'order_type', |
| 178 | + 'quantity', 'product', 'validity', 'price', 'trigger_price' |
| 179 | + ] |
| 180 | + |
| 181 | + # These columns are common for all orders - columns with a single name |
| 182 | + columns = { |
| 183 | + 'exchange': 'NSE', |
| 184 | + 'product': 'MIS', |
| 185 | + 'validity': 'DAY', |
| 186 | + 'order_type': 'SL' |
| 187 | + } |
| 188 | + |
| 189 | + # These are columns to be renamed |
| 190 | + rename = { |
| 191 | + 'order': 'transaction_type', |
| 192 | + 'price': 'Price', |
| 193 | + 'qty': 'quantity', |
| 194 | + 'trigger_price': 'trigger_price', |
| 195 | + 'price': 'price' , |
| 196 | + 'SYMBOL': 'tradingsymbol' |
| 197 | + } |
| 198 | + |
| 199 | + # Generating orders in the required format |
| 200 | + entry_orders = orders.copy() |
| 201 | + entry = create_orders(entry_orders, rename=rename, **columns) |
| 202 | + |
| 203 | + # Exit orders order type and price to be changed |
| 204 | + exit_orders = orders.copy() |
| 205 | + exit_orders['order'] = 'BUY' |
| 206 | + exit_orders['price'] = 0 |
| 207 | + exit_orders['trigger_price'] = stop_loss(orders['price'], 3, order='S').round(2) |
| 208 | + columns.update({'order_type': 'SL-M'}) |
| 209 | + exit = create_orders(exit_orders, rename=rename, **columns) |
| 210 | + trades = entry.append(exit, sort=False)[cols].to_dict(orient='records') |
| 211 | + |
| 212 | + # Order HTML file generated |
| 213 | + env = Environment(loader=FileSystemLoader('.')) |
| 214 | + template = env.get_template('template.html') |
| 215 | + output_from_parsed_template = template.render(api_key = APIKEY, |
| 216 | + orders=json.dumps(trades), |
| 217 | + date=str(datetime.datetime.now())) |
| 218 | + |
| 219 | + with open('zerodha_order.html', 'w') as f: |
| 220 | + f.write(output_from_parsed_template) |
| 221 | + print('Zerodha order file generated') |
| 222 | + |
| 223 | +if __name__ == "__main__": |
| 224 | + generate_nest() |
| 225 | + generate_zerodha() |
| 226 | + |
| 227 | + |
0 commit comments