├── .github └── workflows │ ├── master_nse-papertrade-api.yml │ └── master_nse-papertrade-app.yml ├── .gitignore ├── .jshintrc ├── README.md ├── Screenshot.png ├── [notworking]buildandrun.sh ├── client ├── .dockerignore ├── .env.sample ├── Dockerfile ├── Dockerfile.backup ├── babel.config.js ├── buildandrun.sh ├── k8s │ ├── deployment.yaml │ └── loadBalancer.yaml ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── favicon.ico │ └── index.html ├── remove.sh ├── src │ ├── App.vue │ ├── assets │ │ ├── grip.svg │ │ └── logo.png │ ├── common │ │ ├── logs.js │ │ └── utility.js │ ├── components │ │ └── ui │ │ │ ├── AutoComplete.vue │ │ │ ├── DropDown.vue │ │ │ ├── GroupButton.vue │ │ │ ├── SwitchButton.vue │ │ │ └── ToolTip.vue │ ├── main.js │ ├── router │ │ └── index.js │ ├── shared │ │ ├── chart.js │ │ ├── index.js │ │ └── resource.js │ ├── store │ │ ├── index.js │ │ ├── modules │ │ │ ├── data.js │ │ │ ├── portfolio.js │ │ │ ├── strategy.js │ │ │ └── trade.js │ │ └── mutationtype.js │ ├── tailwind.css │ └── views │ │ ├── About.vue │ │ ├── PaperTrade.vue │ │ ├── PortfolioDetail.vue │ │ ├── PortfolioMenu.vue │ │ ├── StrategyDetail.vue │ │ ├── TradeList.vue │ │ └── builder │ │ └── Builder.vue ├── stop.sh ├── tailwind.config.js └── vue.config.js ├── common ├── logs.js └── utility.js ├── deployment.yaml ├── docker-compose.yaml ├── package-lock.json ├── package.json └── server ├── .dockerignore ├── .env.sample ├── .gitignore ├── Dockerfile ├── Dockerfile.backup ├── buildandrun.sh ├── index.html ├── k8s ├── deployment.yaml └── loadBalancer.yaml ├── package-lock.json ├── package.json ├── remove.sh ├── src ├── common │ ├── logs.js │ └── utility.js ├── controller │ ├── portfoliocotroller.js │ ├── strategycontroller.js │ └── tradecontroller.js ├── dataprovidercontroller │ ├── cboe.js │ ├── index.js │ └── nse.js ├── index.js ├── models │ ├── commonUtility.js │ ├── portfolio.js │ ├── strategy.js │ └── trade.js └── swagger.json ├── stop.sh └── webpack.config.js /.github/workflows/master_nse-papertrade-api.yml: -------------------------------------------------------------------------------- 1 | # Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy 2 | # More GitHub Actions for Azure: https://github.com/Azure/actions 3 | 4 | name: API 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Set up Node.js version 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: '16.x' 23 | 24 | - name: npm install server 25 | run: | 26 | npm install --prefix ./server 27 | npm run build --prefix ./server 28 | 29 | - name: Upload artifact for deployment job 30 | uses: actions/upload-artifact@v2 31 | with: 32 | name: node-app 33 | path: ./server/dist/ 34 | 35 | deploy: 36 | runs-on: ubuntu-latest 37 | needs: build 38 | environment: 39 | name: 'production' 40 | url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} 41 | 42 | steps: 43 | - name: Download artifact from build job 44 | uses: actions/download-artifact@v2 45 | with: 46 | name: node-app 47 | 48 | - name: 'Deploy to Azure Web Api' 49 | id: deploy-to-webapi 50 | uses: azure/webapps-deploy@v2 51 | with: 52 | app-name: 'nse-papertrade-api' 53 | slot-name: 'production' 54 | publish-profile: ${{ secrets.AzureAppService_PublishProfile_83f24ac787e64ddc82b30dbb09fb6650 }} 55 | package: . 56 | -------------------------------------------------------------------------------- /.github/workflows/master_nse-papertrade-app.yml: -------------------------------------------------------------------------------- 1 | # Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy 2 | # More GitHub Actions for Azure: https://github.com/Azure/actions 3 | 4 | name: Application 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Set up Node.js version 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: '16.x' 23 | 24 | - name: npm install client 25 | run: | 26 | npm install --prefix ./client 27 | npm run build --prefix ./client 28 | 29 | - name: Upload artifact for deployment job 30 | uses: actions/upload-artifact@v2 31 | with: 32 | name: node-app 33 | path: ./client/dist/ 34 | 35 | deploy: 36 | runs-on: ubuntu-latest 37 | needs: build 38 | environment: 39 | name: 'Production' 40 | url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} 41 | 42 | steps: 43 | - name: Download artifact from build job 44 | uses: actions/download-artifact@v2 45 | with: 46 | name: node-app 47 | 48 | - name: 'Deploy to Azure Web App' 49 | id: deploy-to-webapp 50 | uses: azure/webapps-deploy@v2 51 | with: 52 | app-name: 'nse-papertrade-app' 53 | slot-name: 'Production' 54 | publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_D6FB5329CA334297BECA34EFBC65D326 }} 55 | package: . 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | launch.json 5 | DockerCommands.txt 6 | build.log 7 | node.core 8 | 9 | 10 | 11 | # local env files 12 | .env 13 | .env.local 14 | .env.*.local 15 | 16 | # Log files 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | 21 | # Editor directories and files 22 | .idea 23 | .vscode 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | *.pem 30 | *.code-workspace 31 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 8 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PaperTrade 2 | This is a simple trading simulator that allows you to test your trading strategies without putting any real money at risk. It is inspired by Sensibull papertrade, Tradingview papertrade, and tastyworks. 3 | 4 | 7 | 8 | ## Server 9 | The server folder contains API code and it's runs on port 9090. To start API use `npm run dev` in the server folder. 10 | 11 | ## Client 12 | The client folder contains SPA code and it's runs on port 8080. To start App use `npm run dev` in the client folder. 13 | 14 | BSD and Linux users can run `npm run dev` from the application root directory to start both Client & Server. 15 | 16 | ## Backend 17 | Web API uses MongoDB Atlas, change the connection string in the `.env` file to point to your MongoDB instance (not tested in local instances). 18 | 19 | ## Build 20 | Client build (npm run build) will build the app and move the dist to the '/server/public' folder. 21 | 22 | ## Screenshot 23 | 24 | ![Screenshot](https://raw.githubusercontent.com/anandav/PaperTrade/master/Screenshot.png "Screenshot") 25 | 26 | --- 27 | > :warning: Required nodejs 16 LTS. 28 | > :warning: Make sure to rename `.env.sample` to `.env` files for both server and client 29 | > :warning: chagne the endpoint in server `.env` to get live update from NSE (Unfortunatily I can't share those API urls) 30 | > :warning: Change the endpoint in server's `.env` file to get live updates from NSE. (Unfortunately, I can't share those API URLs.) 31 | > :warning: Delete functionality won't ask for conformation, use 'double click' to delete. 32 | 33 | 34 | -------------------------------------------------------------------------------- /Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anandav/PaperTrade/ef6c665630e4e336bc6d1e348192c0eeb670555a/Screenshot.png -------------------------------------------------------------------------------- /[notworking]buildandrun.sh: -------------------------------------------------------------------------------- 1 | docker build -t ptserver -f ./server/Dockerfile . 2 | docker build -t ptclient -f ./client/Dockerfile . 3 | docker run -dp :9090:9090 ptserver 4 | docker run -dp :8080:8080 ptclient 5 | 6 | # docker pull mongodb/mongodb-community-server:latest 7 | # docker run -dp 27017:27017 mongodb/mongodb-community-server 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /client/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /client/.env.sample: -------------------------------------------------------------------------------- 1 | APP_APIURL=http://localhost:9090/ 2 | -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine AS build 2 | WORKDIR /client 3 | COPY . . 4 | RUN npm install 5 | RUN npm run build 6 | 7 | FROM node:lts-alpine 8 | COPY --from=build /client/dist/ /home/client/ 9 | COPY --from=build /client/.env /home/client/ 10 | RUN npm install -g http-server 11 | EXPOSE 8080 12 | CMD [ "http-server", "/home/client" ] 13 | -------------------------------------------------------------------------------- /client/Dockerfile.backup: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | WORKDIR /client 3 | RUN npm install -g http-server 4 | COPY . . 5 | RUN npm --prefix ./client install 6 | RUN NODE_OPTIONS=--openssl-legacy-provider npm --prefix ./client run build 7 | RUN cp -r ./client/dist/ /home/client 8 | EXPOSE 8080 9 | CMD [ "http-server", "/home/client" ] 10 | -------------------------------------------------------------------------------- /client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /client/buildandrun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build -t ptclient . 4 | docker run -d -p 8080:8080 ptclient 5 | -------------------------------------------------------------------------------- /client/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: ptclient 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: ptclient 9 | replicas: 3 10 | template: 11 | metadata: 12 | labels: 13 | app: ptclient 14 | spec: 15 | containers: 16 | - name: ptclient 17 | image: k8sclusterdevind01registry.azurecr.io/ptclient:002 18 | resources: 19 | limits: 20 | memory: "256Mi" 21 | cpu: "200m" 22 | ports: 23 | - containerPort: 8080 24 | -------------------------------------------------------------------------------- /client/k8s/loadBalancer.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anandav/PaperTrade/ef6c665630e4e336bc6d1e348192c0eeb670555a/client/k8s/loadBalancer.yaml -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "papertrade-client", 3 | "version": "0.1.1", 4 | "private": true, 5 | "description": "Papper Trade Client.", 6 | "scripts": { 7 | "dev": "vue-cli-service serve", 8 | "devcss": "postcss src/tailwind.css -o public/tailwind.css", 9 | "devcss2": "tailwindcss build -i src/tailwind.css -o myindex.css", 10 | "serve": "vue-cli-service serve", 11 | "build": "vue-cli-service build", 12 | "build:css": "postcss src/tailwind.css -o public/tailwind.css", 13 | "lint": "vue-cli-service lint" 14 | }, 15 | "dependencies": { 16 | "@tailwindcss/forms": "^0.3.4", 17 | "axios": "^1.7.9", 18 | "core-js": "^3.6.4", 19 | "d3": "^7.6.1", 20 | "dayjs": "^1.10.7", 21 | "dotenv": "^16.4.7", 22 | "env-paths": "^3.0.0", 23 | "node-static": "^0.7.11", 24 | "vue": "^3.0.0", 25 | "vue-router": "^4.5.0", 26 | "vuex": "^4.1.0" 27 | }, 28 | "devDependencies": { 29 | "@vue/cli-plugin-babel": "~4.3.0", 30 | "@vue/cli-plugin-eslint": "^3.1.1", 31 | "@vue/cli-plugin-router": "^4.5.6", 32 | "@vue/cli-plugin-vuex": "^4.5.6", 33 | "@vue/cli-service": "^4.5.13", 34 | "autoprefixer": "^9.8.6", 35 | "babel-eslint": "^10.1.0", 36 | "dotenv": "^16.4.7", 37 | "eslint": "^6.7.2", 38 | "eslint-plugin-vue": "^6.2.2", 39 | "postcss": "^8.4.49", 40 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.9", 41 | "vue-template-compiler": "^2.6.12" 42 | }, 43 | "eslintConfig": { 44 | "root": true, 45 | "env": { 46 | "node": true 47 | }, 48 | "extends": [ 49 | "plugin:vue/essential", 50 | "eslint:recommended" 51 | ], 52 | "parserOptions": { 53 | "parser": "babel-eslint" 54 | }, 55 | "rules": {} 56 | }, 57 | "browserslist": [ 58 | "> 1%", 59 | "last 2 versions", 60 | "not dead" 61 | ], 62 | "author": "Anand.AV", 63 | "license": "UNLICENSED" 64 | } 65 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anandav/PaperTrade/ef6c665630e4e336bc6d1e348192c0eeb670555a/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <%= htmlWebpackPlugin.options.title %> 12 | 13 | 14 | 15 | 16 | 17 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /client/remove.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo docker stop $(sudo docker ps -a -q --filter ancestor='ptclient') 4 | sudo docker rm $(sudo docker ps -a -q --filter ancestor='ptclient') 5 | sudo docker rmi 'ptclient' 6 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 94 | 95 | 97 | -------------------------------------------------------------------------------- /client/src/assets/grip.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 38 | 42 | 43 | 45 | 49 | 53 | 55 | 59 | 63 | 67 | 71 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 103 | 107 | 111 | 115 | 119 | 123 | 127 | 131 | 135 | 139 | 143 | 147 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anandav/PaperTrade/ef6c665630e4e336bc6d1e348192c0eeb670555a/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/common/logs.js: -------------------------------------------------------------------------------- 1 | const utility = require("./utility"); 2 | 3 | module.exports = new function () { 4 | this.log = (...message) => { 5 | this.info(message); 6 | } 7 | this.info = (...message) => { 8 | console.log(utility.formatDate(), 'INFO:', message.join(' ')); 9 | } 10 | this.warn = (...message) => { 11 | // let isString = message => typeof message === 'string' || message instanceof String; 12 | // console.warn(isString()); 13 | if (message.length == 1) { 14 | console.warn(utility.formatDate(), 'WARNING:', message); 15 | } else { 16 | console.warn(utility.formatDate(), 'WARNING:', message.join(' ')); 17 | } 18 | } 19 | this.error = (...message) => { 20 | console.error(utility.formatDate(), 'ERROR:', message.join(' ')); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /client/src/common/utility.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | formatDate: () => { 3 | //// AI Generated 4 | var date = new Date(); 5 | const year = date.getFullYear(); 6 | const month = module.exports.getMonthName(date.getMonth()); 7 | const day = String(date.getDate()).padStart(2, '0'); 8 | const hours = String(date.getHours()).padStart(2, '0'); 9 | const minutes = String(date.getMinutes()).padStart(2, '0'); 10 | const seconds = String(date.getSeconds()).padStart(2, '0'); 11 | const ms = String(date.getMilliseconds()).padStart(3, '0'); 12 | 13 | return `${day}-${month}-${year} ${hours}:${minutes}:${seconds}.${ms}`; 14 | }, 15 | 16 | getMonthName: (monthIndex) => { 17 | //// AI Generated 18 | const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 19 | return monthNames[monthIndex]; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /client/src/components/ui/AutoComplete.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 127 | 128 | -------------------------------------------------------------------------------- /client/src/components/ui/DropDown.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 119 | 124 | -------------------------------------------------------------------------------- /client/src/components/ui/GroupButton.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /client/src/components/ui/SwitchButton.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 104 | 105 | -------------------------------------------------------------------------------- /client/src/components/ui/ToolTip.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp }from 'vue'; 2 | // import { createApp } from 'vue' 3 | import App from "./App.vue"; 4 | import router from "./router"; 5 | import store from "./store"; 6 | // import dotenv from 'dotenv' 7 | import resource from "./shared/resource"; 8 | import "./tailwind.css"; 9 | //import dayjs from "dayjs"; 10 | import dropdown from "./components/ui/DropDown"; 11 | import autocomplete from "./components/ui/AutoComplete"; 12 | import tooltip from "./components/ui/ToolTip"; 13 | 14 | 15 | // Vue.filter("formatDateTime", function (value) { 16 | // if (value) { 17 | // var _format = value.length <= 10 ? "DD-MMM-YYYY" : "DD-MMM-YYYY HH:mm"; 18 | // return dayjs(value, ["YYYY", "YYYY-MM-DD"], "in", true).format(_format); 19 | // } 20 | // }); 21 | // Vue.filter("formatDate", function (value) { 22 | // if (value) { 23 | // console.log('input value :>> ', value); 24 | // var result = dayjs(value, ["YYYY", "YYYY-MM-DD"], "in", true).format("DD-MMM-YYYY"); 25 | // console.log('result value :>> ', result); 26 | // return result; 27 | // } 28 | // }); 29 | // Vue.filter("decimal2", function (value) { 30 | // if (value) { 31 | // return parseFloat(value).toFixed(2); 32 | // } 33 | // }); 34 | 35 | const app = createApp(App); 36 | app.config.productionTip = false; 37 | app.config.keyCodes = { 38 | f2: 113, 39 | }; 40 | 41 | 42 | app.use(router); 43 | app.use(store); 44 | app.provide('GETCONST',resource); 45 | app.component("autocomplete", autocomplete); 46 | app.component("dropdown", dropdown); 47 | app.component("tooltip", tooltip); 48 | 49 | app.mount('#app'); 50 | 51 | 52 | // 53 | //new Vue({ 54 | // router, 55 | // store, 56 | // 57 | // render: (h) => h(App), 58 | //}).$mount("#app"); 59 | -------------------------------------------------------------------------------- /client/src/router/index.js: -------------------------------------------------------------------------------- 1 | //import * as Vue from 'vue'; 2 | import { createRouter, createWebHistory } from 'vue-router'; 3 | import PaperTrade from '../views/PaperTrade'; 4 | import Builder from '../views/builder/Builder'; 5 | 6 | const routes = [ 7 | { 8 | path: '/', 9 | component: PaperTrade, 10 | 11 | }, 12 | { 13 | path: '/builder', 14 | name: 'Builder', 15 | component: Builder 16 | }, 17 | { 18 | path: '/about', 19 | name: 'About', 20 | },{ 21 | path: '/:pathMatch(.*)*', 22 | redirect: '/' 23 | }] 24 | 25 | const router = createRouter({ 26 | history : createWebHistory(), 27 | routes 28 | }); 29 | // const router = new VueRouter({ 30 | // mode: 'history', 31 | // base: process.env.BASE_URL, 32 | // routes : approutes 33 | // }) 34 | 35 | export default router 36 | -------------------------------------------------------------------------------- /client/src/shared/chart.js: -------------------------------------------------------------------------------- 1 | import * as d3 from "d3"; 2 | //import logs, { log } from "../common/logs" 3 | const utilitymixins = { 4 | data: function () { 5 | return { 6 | MARGIN: { 7 | LEFT: 50, 8 | RIGHT: 50, 9 | TOP: 10, 10 | BOTTOM: 30, 11 | }, 12 | ChartSettings: { 13 | TOOLTIP: true, 14 | PATTERN: true, 15 | OFFSET: true, 16 | TOOLTIPLOCATION: "FOLLOW", //"BOTTOM",//"FOLLOW",//"TOP" 17 | COLOURS: { 18 | Line: "stroke-current text-yellow-500 ", 19 | // XScale: " ", 20 | // YScale: " ", 21 | PositiveToolTip: "fill-current text-green-700 opacity-80", 22 | //PositiveToolTipText: "stroke-current text-black", 23 | NegativeToolTip: "fill-current text-red-700 opacity-80", 24 | //NegativeToolTipText: "stroke-current text-white", 25 | ToolTipDot: "fill-current text-white opacity-80", 26 | ToolTipDotInnerGreen: "fill-current text-green-600", 27 | ToolTipDotInnerRed: "fill-current text-red-600", 28 | ToolTipLine: 29 | "stroke-current text-gray-500 dark:text-gray-500 opacity-50", 30 | PositiveRegion: "fill-current text-green-700 opacity-20 ", 31 | NegativeRegion: "fill-current text-red-700 opacity-20", 32 | Positive: "#3DB2FF", 33 | PositiveRegionOnlyOpacity: "opacity-30", 34 | Nevgative: "#FF2442", 35 | NegativeRegionOnlyOpacity: "opacity-30", 36 | LGGREEN: "rgb(57, 163, 136)", 37 | LGRED: "rgb(224, 36, 1)", 38 | }, 39 | DIMENSION: { 40 | Line: 2, 41 | }, 42 | }, 43 | WIDTH: 500, 44 | HEIGHT: 300, 45 | BUYORSELL: { 46 | 1: "Buy", 47 | 2: "Sell", 48 | }, 49 | TRADETYPE: { 50 | 1: "Call", 51 | 2: "Put", 52 | 3: "Future", 53 | 4: "Equity", 54 | // 5: "Crypto" 55 | }, 56 | }; 57 | }, 58 | methods: { 59 | GenerateChart: function (strategy) { 60 | let paretnId = 61 | "#strategy_" + strategy._id + " .chartplaceholder .chart"; 62 | if (this.hasDerivative(strategy)) { 63 | let chartData = this.GenerateChartPoint(strategy); 64 | //let chartDatawithoutExit = this.GenerateChartPoint(strategy, true); 65 | // logs.warn(chartData); 66 | // logs.warn(chartDatawithoutExit); 67 | d3.selectAll(paretnId + " > *").remove(); 68 | if (chartData?.length > 0) { 69 | this.GenerateLineChart(paretnId, strategy, chartData); 70 | } else { 71 | let placeholder = [ 72 | { 73 | strikePrice: 1, 74 | symbol: "Nifty", 75 | name: "Strategy - 1", 76 | //"intrinsicValue": 0, 77 | PnL: 0, 78 | netPnL: 0, 79 | qty: 0, 80 | lot: 0, 81 | price: 0, 82 | }, 83 | ]; 84 | this.GenerateLineChart(paretnId, strategy, placeholder); 85 | } 86 | } 87 | else { 88 | //console.clear(); 89 | //console.log('No Data :>> '); 90 | strategy.HideChart = false; 91 | return false; 92 | } 93 | }, 94 | 95 | GetBreakEven: function (strategy) { 96 | //let tmaxSP = d3.max(strategy.trades, d => d.selectedstrike); 97 | let netPnlArr = []; 98 | for (let i = 0, len = strategy.trades.length; i < len; i++) { 99 | let currentTrade = strategy.trades[i]; 100 | //let netPnL = 0, PnL = 0; 101 | for (let j = 0, len2 = strategy.trades.length; j < len2; j++) { 102 | let currentTrade2 = strategy.trades[j]; 103 | let obj = this.getNetPnL(currentTrade.selectedstrike, currentTrade2); 104 | netPnlArr[i] += obj.netPnL; 105 | //PnL += obj.PnL; 106 | } 107 | } 108 | }, 109 | 110 | GetMaxMinPnL: function (strategy) { 111 | let tminSP = d3.min(strategy.trades, (d) => d.selectedstrike); 112 | //let tmaxSP = d3.max(strategy.trades, d => d.selectedstrike); 113 | let minPrice = [tminSP - 1, tminSP]; 114 | let minStriketrade; //strategy.trades.find(t => t.selectedstrike == tminSP); 115 | //let maxStriketrade = strategy.trades.find(t => t.selectedstrike == tmaxSP); 116 | let tradeCount = strategy.trades.length; 117 | 118 | minPrice.forEach((j) => { 119 | for (let i = 0; i < tradeCount; i++) { 120 | let PnlObj = this.getNetPnL(j, strategy.trades[i]); 121 | 122 | if (minStriketrade) { 123 | minStriketrade.netPnL += PnlObj.netPnL; 124 | minStriketrade.PnL = PnlObj.PnL; 125 | } else { 126 | minStriketrade = { 127 | strikePrice: j, 128 | qty: strategy.trades[i].quantity, 129 | lot: strategy.trades[i].lotsize, 130 | price: strategy.trades[i].price, 131 | ...PnlObj, 132 | }; 133 | } 134 | } 135 | }); 136 | }, 137 | 138 | getoffsetprices: function (leastPrice) { 139 | let _min = Math.min(...leastPrice), 140 | _max = Math.max(...leastPrice); 141 | let strikepricemin = this.getdigits(_min, "min"), 142 | strikepricemax = this.getdigits(_max, "max"); 143 | let incrementby = this.getdigits((_min + _max) / 2, "increment"); 144 | return { x0: strikepricemin, x1: strikepricemax, xstep: incrementby }; 145 | }, 146 | 147 | getdigits: function (value, ismin) { 148 | let _valdiglen = this.ChartSettings.OFFSET 149 | ? Math.ceil(Math.log10(value + 1)) 150 | : 0; 151 | let offset = 0; 152 | let incrementby = 1; 153 | switch (_valdiglen) { 154 | case 1: 155 | offset = 0.05; 156 | incrementby = 0.01; 157 | break; 158 | case 2: 159 | offset = 1; 160 | incrementby = 0.01; 161 | break; 162 | case 3: 163 | offset = 10; 164 | incrementby = 0.1; 165 | break; 166 | case 4: 167 | offset = 100; 168 | incrementby = 5; 169 | break; 170 | case 5: 171 | offset = value < 20000 ? 500 : 1200; 172 | incrementby = 10; 173 | break; 174 | default: 175 | offset = 0; 176 | incrementby = 1; 177 | break; 178 | } 179 | if (ismin == "min") { 180 | value -= offset; 181 | } else if (ismin == "max") { 182 | value += offset; 183 | } else if (ismin == "increment") { 184 | value = incrementby; 185 | } 186 | return value; 187 | }, 188 | 189 | getAllStrikePrices: function (strategy) { 190 | let strikePrices = strategy.trades.map((t) => { 191 | if (t.tradetype == "Future") { 192 | return t.price; 193 | } else { 194 | return t.selectedstrike; 195 | } 196 | }); 197 | return strikePrices; 198 | }, 199 | 200 | getNetPnL: function (_strikePrice, currentTrade, strategy) { 201 | let _intrinsicValue = 0, 202 | PnL = 0, 203 | netPnL = 0; 204 | 205 | if (currentTrade.tradetype == "Call") { 206 | _intrinsicValue = 207 | _strikePrice - currentTrade.selectedstrike > 0 208 | ? _strikePrice - currentTrade.selectedstrike 209 | : 0; 210 | } else if (currentTrade.tradetype == "Put") { 211 | _intrinsicValue = 212 | currentTrade.selectedstrike - _strikePrice > 0 213 | ? currentTrade.selectedstrike - _strikePrice 214 | : 0; 215 | } 216 | 217 | if (currentTrade.tradetype == "Future") { 218 | PnL = 219 | currentTrade.buyorsell == "Buy" 220 | ? _strikePrice - currentTrade.price 221 | : currentTrade.price - _strikePrice; 222 | netPnL = currentTrade.quantity * strategy.lotsize * PnL; 223 | } else { 224 | PnL = 225 | currentTrade.buyorsell == "Buy" 226 | ? _intrinsicValue - currentTrade.price 227 | : currentTrade.price - _intrinsicValue; 228 | netPnL = currentTrade.quantity * strategy.lotsize * PnL; 229 | netPnL = parseFloat(netPnL.toFixed(2)); 230 | } 231 | return { PnL, netPnL }; 232 | }, 233 | 234 | GenerateChartPoint: function (strategy, skipExit ) { 235 | if (strategy && strategy.trades && strategy.trades.length > 0) { 236 | let range = { 237 | x0: parseFloat(strategy.x0), 238 | x1: parseFloat(strategy.x1), 239 | }; 240 | let tradeCount = strategy.trades.length; 241 | let chartData = []; 242 | let strikePrices = this.getAllStrikePrices(strategy); 243 | /// Removes undefined from strikePrices 244 | strikePrices = strikePrices.filter(Boolean); 245 | let _range = this.getoffsetprices(strikePrices); 246 | let xStep = _range.xstep; 247 | range = { 248 | x0: isNaN(range.x0) ? _range.x0 : range.x0, 249 | x1: isNaN(range.x1) ? _range.x1 : range.x1, 250 | }; 251 | for (let i = 0; i < tradeCount; i++) { 252 | if (!strategy.trades[i].checked) { 253 | continue; 254 | } 255 | let currentTrade = strategy.trades[i]; 256 | 257 | if(currentTrade.isexit && skipExit){ 258 | continue; 259 | } 260 | 261 | if ( 262 | currentTrade.tradetype == "Call" || 263 | currentTrade.tradetype == "Put" || 264 | currentTrade.tradetype == "Future" 265 | ) { 266 | let _strikePrice = range.x0; 267 | let j = 0; 268 | do { 269 | let PnlObj = this.getNetPnL(_strikePrice, currentTrade, strategy); 270 | if (chartData[j]) { 271 | chartData[j].netPnL += PnlObj.netPnL; 272 | chartData[j].PnL = PnlObj.PnL; 273 | } else { 274 | chartData.push({ 275 | strikePrice: parseFloat(_strikePrice.toFixed(2)), 276 | qty: currentTrade.quantity, 277 | price: currentTrade.price, 278 | ...PnlObj, 279 | }); 280 | } 281 | j += 1; 282 | _strikePrice += xStep; 283 | } while (range.x1 >= _strikePrice); 284 | } 285 | } 286 | return chartData; 287 | } 288 | }, 289 | 290 | hasDerivative: function (strategy) { 291 | const s = (e) => 292 | e.tradetype == "Call" || 293 | e.tradetype == "Put" || 294 | e.tradetype == "Future"; 295 | return strategy.trades.some(s); 296 | }, 297 | 298 | /// Line Chart 299 | /// Ref: https://observablehq.com/@simulmedia/line-chart 300 | /// Ref: https://gist.github.com/llad/3766585 301 | /// Reg: http://jsfiddle.net/samselikoff/Jqmzd/2/ 302 | /// Ref: https://observablehq.com/@elishaterada/simple-area-chart-with-tooltip 303 | /// Ref: https://observablehq.com/@jlchmura/d3-change-line-chart-with-positive-negative-fill 304 | GenerateLineChart: function (paretnId, strategy, chartData) { 305 | if (!chartData || !paretnId) return; 306 | const _WIDTH = document.querySelectorAll(paretnId)[0].clientWidth; 307 | // const parentObj = document.querySelector(paretnId); 308 | // const __node1 = document.createElement("input") 309 | // __node1.setAttribute("class","chart-mini-edit ml-12") 310 | //parentObj.append(__node1) 311 | this.WIDTH = _WIDTH > 0 ? _WIDTH : this.WIDTH; 312 | const minPnL = d3.min(chartData, (d) => d.netPnL); 313 | const maxPnL = d3.max(chartData, (d) => d.netPnL); 314 | const minSP = d3.min(chartData, (d) => d.strikePrice); 315 | const maxSP = d3.max(chartData, (d) => d.strikePrice); 316 | 317 | const xScale = d3 318 | .scaleLinear() 319 | .domain([minSP, maxSP]) 320 | .range([this.MARGIN.LEFT, this.WIDTH]); 321 | const yScale = d3 322 | .scaleLinear() 323 | .domain([minPnL, maxPnL]) 324 | .nice() 325 | .range([this.HEIGHT - this.MARGIN.BOTTOM, this.MARGIN.TOP]); 326 | const xAxisCall = d3.axisBottom(xScale); 327 | const yAxisCall = d3 328 | .axisLeft(yScale) 329 | .ticks(10) 330 | .tickFormat(d3.formatPrefix("0.1", 1e5)); 331 | const areaPos = d3 332 | .area() 333 | .curve(d3.curveLinear) 334 | .x((d) => xScale(d.strikePrice)) 335 | .y0(yScale(1)) 336 | .y1((d) => yScale(Math.max(0.1, d.netPnL))); 337 | const areaNeg = d3 338 | .area() 339 | .curve(d3.curveLinear) 340 | .x((d) => xScale(d.strikePrice)) 341 | .y0(yScale(1)) 342 | .y1((d) => yScale(Math.min(0.1, d.netPnL))); 343 | const bisect = d3.bisector((d) => d.strikePrice).left; 344 | 345 | const callout = (g, val, xInv) => { 346 | if (!val) return g.style("display", "none"); 347 | const { height } = g.node().getBBox(); 348 | let pnlval = val.netPnL.toFixed(2); 349 | let tooltipText = ""; 350 | let transYScale = 0; 351 | const path = g 352 | .selectAll("path") 353 | .data([null]) 354 | .join("path") 355 | .attr( 356 | "class", 357 | val.netPnL > 0 358 | ? this.ChartSettings.COLOURS.PositiveToolTip 359 | : this.ChartSettings.COLOURS.NegativeToolTip 360 | ); 361 | 362 | if (this.ChartSettings.TOOLTIPLOCATION == "BOTTOM") { 363 | tooltip.attr( 364 | "transform", 365 | `translate(${xScale(xInv)},${this.HEIGHT - height})` 366 | ); 367 | tooltipText = `P&L:${pnlval}\nStrike:${xInv}`; 368 | } else if (this.ChartSettings.TOOLTIPLOCATION == "FOLLOW") { 369 | transYScale = yScale(pnlval); 370 | let _transYScale = transYScale; 371 | if (pnlval < 0) { 372 | transYScale -= 50; 373 | _transYScale = transYScale; 374 | _transYScale = _transYScale < 0 ? _transYScale + 55 : _transYScale; 375 | } else { 376 | _transYScale = 377 | transYScale >= this.HEIGHT - this.MARGIN.BOTTOM 378 | ? transYScale - 55 379 | : transYScale; 380 | } 381 | tooltip.attr( 382 | "transform", 383 | `translate(${xScale(xInv)},${_transYScale})` 384 | ); 385 | tooltipText = `P&L:${pnlval}\nStrike:${xInv}`; 386 | } else if (this.ChartSettings.TOOLTIPLOCATION == "TOP") { 387 | tooltip.attr( 388 | "transform", 389 | `translate(${xScale(xInv)},${-height + 35})` 390 | ); 391 | tooltipText = `P&L:${pnlval}\nStrike:${xInv}`; 392 | } 393 | 394 | g.style("display", null) 395 | .style("pointer-events", "none") 396 | .style("font", "10px sans-serif"); 397 | const text = g 398 | .selectAll("text") 399 | .data([null]) 400 | .join("text") 401 | .call((text) => 402 | text 403 | .selectAll("tspan") 404 | .data((tooltipText + "").split(/\n/)) 405 | .join("tspan") 406 | .attr("x", 0) 407 | .attr("y", (d, i) => `${i * 1.1}em`) 408 | .style("font-weight", (_, i) => (i ? null : "bold")) 409 | .style("fill", "white") 410 | .text((d) => d) 411 | ); 412 | const { y, width: w, height: h } = text.node().getBBox(); 413 | text.attr("transform", `translate(${-w / 2},${15 - y})`); 414 | 415 | const upArrow = `M${-w / 2 - 10},5H-5l5,-5l5,5H${w / 2 + 10}v${h + 416 | 20}h-${w + 20}z`; 417 | const downArrow = `M ${w / 2 + 10},42 H 5 L 0,47 -5,42 H ${-w / 2 - 418 | 10} v ${h - 60} h ${w + 20}z`; 419 | if (this.ChartSettings.TOOLTIPLOCATION == "BOTTOM") { 420 | path.attr("d", upArrow); 421 | } else if (this.ChartSettings.TOOLTIPLOCATION == "FOLLOW") { 422 | if (transYScale < 0) { 423 | path.attr("d", upArrow); 424 | } else { 425 | if (transYScale >= this.HEIGHT - this.MARGIN.BOTTOM) { 426 | path.attr("d", downArrow); 427 | } else { 428 | path.attr("d", val.netPnL >= 0 ? upArrow : downArrow); 429 | } 430 | } 431 | } else if (this.ChartSettings.TOOLTIPLOCATION == "TOP") { 432 | path.attr("d", downArrow); 433 | } 434 | }; 435 | const onMouseMove = (e) => { 436 | const mouse = d3.pointer(e); 437 | const [x0] = mouse; 438 | const xInv = parseFloat(xScale.invert(x0).toFixed(2)); 439 | if ( 440 | xScale(xInv) < this.MARGIN.LEFT || 441 | xScale(xInv) > this.WIDTH + this.MARGIN.RIGHT 442 | ) { 443 | return; 444 | } 445 | const xIndex = bisect(chartData, xInv, 1); 446 | const a = chartData[xIndex - 1]; 447 | const b = chartData[xIndex]; 448 | const val = b && xInv - a.strikePrice > b.strikePrice - xInv ? b : a; 449 | 450 | tooltipline 451 | .attr("x1", xScale(xInv)) 452 | .attr("y1", this.MARGIN.TOP) 453 | .attr("x2", xScale(xInv)) 454 | .attr("y2", this.HEIGHT - this.MARGIN.BOTTOM) 455 | .classed(this.ChartSettings.COLOURS.ToolTipLine, true) 456 | .attr("stroke-dasharray", "10,2"); 457 | 458 | tooltipdot 459 | .attr("cx", xScale(xInv)) 460 | .attr("cy", yScale(val.netPnL)) 461 | .attr("r", "4") 462 | .classed(this.ChartSettings.COLOURS.ToolTipDot, true); 463 | 464 | tooltipdotinner 465 | .attr("cx", xScale(xInv)) 466 | .attr("cy", yScale(val.netPnL)) 467 | .attr("r", "6") 468 | .classed(this.ChartSettings.COLOURS.ToolTipDotInnerGreen, false) 469 | .classed(this.ChartSettings.COLOURS.ToolTipDotInnerRed, false) 470 | .classed( 471 | val.netPnL >= 0 472 | ? this.ChartSettings.COLOURS.ToolTipDotInnerGreen 473 | : this.ChartSettings.COLOURS.ToolTipDotInnerRed, 474 | true 475 | ); 476 | 477 | tooltip.call(callout, val, xInv); 478 | }; 479 | const onMouseLeave = () => { 480 | svg 481 | .selectAll(".hovertooltip, .hoverline, .hoverdot") 482 | .attr("visibility", "hidden"); 483 | }; 484 | const onMouseEnter = () => { 485 | svg 486 | .selectAll(".hovertooltip, .hoverline, .hoverdot") 487 | .attr("visibility", "visible"); 488 | }; 489 | const svg = d3 490 | .select(paretnId) 491 | .append("svg") 492 | .attr("class", "line") 493 | .attr("width", this.WIDTH) 494 | .attr("height", this.HEIGHT); 495 | 496 | const line = d3 497 | .line() 498 | .defined((d) => !isNaN(d.netPnL)) 499 | .x((d) => xScale(d.strikePrice)) 500 | .y((d) => yScale(d.netPnL)); 501 | 502 | const lgdefID = `lg_${strategy._id}`; 503 | //const lgdefIDexit = `lgexit_${strategy._id}`; 504 | const lgurlid = `url(#${lgdefID})`; 505 | 506 | svg.attr("stroke-width", this.ChartSettings.DIMENSION.Line); 507 | 508 | svg 509 | .append("linearGradient") 510 | .attr("id", lgdefID) 511 | .attr("gradientUnits", "userSpaceOnUse") 512 | .attr("x1", 0) 513 | .attr("x2", this.WIDTH + this.MARGIN.LEFT + this.MARGIN.RIGHT) 514 | .selectAll("stop") 515 | .data(chartData) 516 | .join("stop") 517 | .attr("offset", (d) => { 518 | return ( 519 | xScale(d.strikePrice) / 520 | (this.WIDTH + this.MARGIN.LEFT + this.MARGIN.RIGHT) 521 | ); 522 | }) 523 | .attr("stop-color", (d) => { 524 | return d.netPnL >= 0 525 | ? this.ChartSettings.COLOURS.LGGREEN 526 | : this.ChartSettings.COLOURS.LGRED; 527 | }); 528 | 529 | // svg 530 | // .append("linearGradient") 531 | // .attr("id", lgdefIDexit) 532 | // .attr("gradientUnits", "userSpaceOnUse") 533 | // .attr("x1", 0) 534 | // .attr("x2", this.WIDTH + this.MARGIN.LEFT + this.MARGIN.RIGHT) 535 | // .selectAll("stop") 536 | // .data(chartDatawithoutExit) 537 | // .join("stop") 538 | // .attr("offset", (d) => { 539 | // return ( 540 | // xScale(d.strikePrice) / 541 | // (this.WIDTH + this.MARGIN.LEFT + this.MARGIN.RIGHT) 542 | // ); 543 | // }) 544 | // .attr("stop-color", (d) => { 545 | // console.log("d.netPnL2"); 546 | // console.log(d.netPnL); 547 | // return d.netPnL >= 0 548 | // ? this.ChartSettings.COLOURS.LGGREEN 549 | // : this.ChartSettings.COLOURS.LGRED; 550 | // }); 551 | 552 | svg 553 | .append("g") 554 | .attr("class", "y axis") 555 | .attr("transform", `translate(${this.MARGIN.LEFT}, 0)`) 556 | .call(yAxisCall) 557 | .selectAll("text") 558 | .attr("dx", "-5"); 559 | 560 | // svg.append("g") 561 | // .attr("class", "x axis zero") 562 | // .attr("stroke", "#fff") 563 | // .attr("transform", `translate(0, ${yScale(0)})`) 564 | // .call(xAxisCall.tickSize(0).tickFormat("")); 565 | 566 | const yDomain = yScale.domain(); 567 | if ((yDomain[0] <= 0 && 0 <= yDomain[1]) || yDomain[0] == yDomain[1]) { 568 | svg 569 | .append("g") 570 | .attr("class", "x axis zero") 571 | .attr("transform", `translate(0, ${yScale(0)})`) 572 | .call(xAxisCall) 573 | .selectAll("text") 574 | .style("text-anchor", "begin") 575 | .attr("dx", "2em") 576 | .attr("dy", "0em") 577 | .attr("transform", "rotate(40)"); 578 | } else { 579 | svg 580 | .append("g") 581 | .attr("class", "x axis") 582 | .attr( 583 | "transform", 584 | `translate(0, ${this.HEIGHT - this.MARGIN.BOTTOM})` 585 | ) 586 | .call(xAxisCall) 587 | .selectAll("text") 588 | .style("text-anchor", "begin") 589 | .attr("dx", "2em") 590 | .attr("dy", "0em") 591 | .attr("transform", "rotate(40)"); 592 | } 593 | 594 | svg 595 | .append("path") 596 | .datum(chartData) 597 | .attr("fill", "none") 598 | .attr("stroke", lgurlid) 599 | .attr("stroke-width", this.ChartSettings.DIMENSION.Line) 600 | .attr("transform", "translate(0,0)") 601 | .attr("stroke-linejoin", "round") 602 | .attr("stroke-linecap", "round") 603 | .attr("d", line); 604 | 605 | // svg 606 | // .append("path") 607 | // .datum(chartDatawithoutExit) 608 | // .attr("fill", "none") 609 | // .attr("stroke", lgurlid) 610 | // .attr("stroke-width", this.ChartSettings.DIMENSION.Line) 611 | // .attr("transform", "translate(0,0)") 612 | // .attr("stroke-linejoin", "round") 613 | // .attr("stroke-linecap", "round") 614 | // .attr("d", line); 615 | 616 | 617 | 618 | 619 | if (this.ChartSettings.PATTERN) { 620 | svg 621 | .append("defs") 622 | .append("pattern") 623 | .attr("id", "green") 624 | .attr("patternUnits", "userSpaceOnUse") 625 | .attr("width", 4) 626 | .attr("height", 4) 627 | .append("path") 628 | .attr("d", "M-1,1 l2,-2 M0,4 l4,-4 M3,5 l2,-2") 629 | .attr("stroke", this.ChartSettings.COLOURS.Positive) 630 | .attr("stroke-width", 1); 631 | 632 | svg 633 | .append("defs") 634 | .append("pattern") 635 | .attr("id", "saffron") 636 | .attr("patternUnits", "userSpaceOnUse") 637 | .attr("width", 4) 638 | .attr("height", 4) 639 | .append("path") 640 | .attr("d", "M-1,1 l2,-2 M0,4 l4,-4 M3,5 l2,-2") 641 | .attr("stroke", this.ChartSettings.COLOURS.Nevgative) 642 | .attr("stroke-width", 1); 643 | 644 | svg 645 | .append("path") 646 | .datum(chartData) 647 | .attr("fill", "url(#green)") 648 | .attr("class", this.ChartSettings.COLOURS.PositiveRegionOnlyOpacity) 649 | .attr("d", areaPos); 650 | 651 | svg 652 | .append("path") 653 | .datum(chartData) 654 | .attr("fill", "url(#saffron)") 655 | .attr("class", this.ChartSettings.COLOURS.NegativeRegionOnlyOpacity) 656 | .attr("d", areaNeg); 657 | } else { 658 | svg 659 | .append("path") 660 | .datum(chartData) 661 | .attr("class", this.ChartSettings.COLOURS.PositiveRegion) 662 | .attr("d", areaPos); 663 | 664 | svg 665 | .append("path") 666 | .datum(chartData) 667 | .attr("class", this.ChartSettings.COLOURS.NegativeRegion) 668 | .attr("d", areaNeg); 669 | } 670 | const tooltipline = svg.append("line").classed("hoverline", true); 671 | const tooltipdotinner = svg.append("circle").classed("hoverdot", true); 672 | const tooltipdot = svg.append("circle").classed("hoverdot", true); 673 | const tooltip = svg.append("g").classed("hovertooltip", true); 674 | if (this.ChartSettings.TOOLTIP) { 675 | svg.on("mousemove", onMouseMove); 676 | svg.on("mouseleave", onMouseLeave); 677 | svg.on("mouseenter", onMouseEnter); 678 | } 679 | }, 680 | }, 681 | }; 682 | export default utilitymixins; 683 | -------------------------------------------------------------------------------- /client/src/shared/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | // import dayjs from 'dayjs'; 3 | // import dropdown from "../components/ui/dropdown"; 4 | // import tooltip from "../components/ui/ToolTip" 5 | 6 | 7 | // Vue.component("dropdown", dropdown); 8 | // Vue.component("tooltip", tooltip); 9 | // Vue.filter('formatDate', function (value) { 10 | // if (value) { 11 | // return dayjs(value, ["YYYY", "YYYY-MM-DD"], 'in', true); 12 | // //moment(String(value)).format('MM/DD/YYYY hh:mm') 13 | // } 14 | // }); 15 | //export * from './resource' 16 | //export * from './utilitymixins'; 17 | 18 | // Resource.install = function (Vue) { 19 | // Vue.prototype.$getConst = (key) => { 20 | // return Resource[key]; 21 | // } 22 | // }; 23 | 24 | const Drag = { 25 | Clone : function(){ 26 | 27 | }, 28 | Move : function(item){ 29 | 30 | } 31 | }; 32 | 33 | export default Drag; 34 | -------------------------------------------------------------------------------- /client/src/shared/resource.js: -------------------------------------------------------------------------------- 1 | export default { 2 | addNewPortfolio: "New Portfolio", 3 | savePortfolio: "Save Portfolio", 4 | deletePortfolio: "Delete Portfolio", 5 | addNewStrategy: "New Strategy", 6 | editStrategy: "Edit Strategy", 7 | deleteStrategy: "Delete Strategy", 8 | saveStrategy: "Save Strategy", 9 | duplicateStrategy: "Duplicate Strategy", 10 | mergeStrategy: "Merge Strategy", 11 | moveStrategy: "Move Strategy", 12 | addTrade: "New Trade", 13 | editTrade : "Edit Trade", 14 | saveTrade : "Save Trade", 15 | exitTrade : "Exit Trade", 16 | deleteTrade : "Delete Trade", 17 | showStrategyDiagram: "Update Graph", 18 | getLiveData : "Get Live Data", 19 | }; -------------------------------------------------------------------------------- /client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore} from "vuex"; 2 | import portfolioModule from './modules/portfolio'; 3 | import strategyModule from "./modules/strategy"; 4 | import tradeModule from './modules/trade'; 5 | import dataModule from './modules/data'; 6 | 7 | 8 | const modules = { portfolioModule, strategyModule, tradeModule, dataModule }; 9 | 10 | export default new createStore({ 11 | modules, 12 | }); 13 | -------------------------------------------------------------------------------- /client/src/store/modules/data.js: -------------------------------------------------------------------------------- 1 | // import "dotenv/config"; 2 | import { 3 | 4 | SETEXCHANGEDETAIL, 5 | SETCURRENTEXCHANGE, 6 | } from "../mutationtype"; 7 | import axios from "axios"; 8 | 9 | const apiUrl = process.env.VUE_APP_APIURL || "/"; 10 | 11 | export default { 12 | namespaced: true, 13 | state: { 14 | CurrentExchange: "", 15 | Exchanges: [], 16 | SymbolTypes: [], 17 | Symbols: [], 18 | }, 19 | getters: { 20 | CurrentExchange: state => { 21 | return state.CurrentExchange; 22 | }, 23 | Exchanges: state => { 24 | return state.Exchanges; 25 | }, 26 | SymbolTypes: state => { 27 | return state.SymbolTypes; 28 | }, 29 | Symbols: state => { 30 | return state.Symbols; 31 | }, 32 | 33 | }, 34 | mutations: { 35 | [SETEXCHANGEDETAIL](state, data) { 36 | state.Exchanges = data.ExchangeDetail.Exchanges; 37 | state.SymbolTypes = data.ExchangeDetail.SymbolTypes; 38 | state.Symbols = data.Symbols; 39 | }, 40 | [SETCURRENTEXCHANGE](state, currentExchange) { 41 | state.CurrentExchange = currentExchange; 42 | }, 43 | }, 44 | actions: { 45 | PortfolioLoad({ commit, getters }, { portfolio, action }) { 46 | let postData = { 47 | portfolio, 48 | action, 49 | }; 50 | let url = `${apiUrl}data/`; 51 | if (portfolio.exchange != getters.CurrentExchange || action == "init") { 52 | axios.post(url, postData).then(function (res) { 53 | if (res.status == 200) { 54 | commit(SETEXCHANGEDETAIL, res.data); 55 | } 56 | }); 57 | if (portfolio.exchange) { 58 | commit(SETCURRENTEXCHANGE, portfolio.exchange); 59 | } 60 | } 61 | }, 62 | GetLiveData({ dispatch }, { portfolio, strategy, action }) { 63 | let postData = { 64 | portfolio, 65 | strategy, 66 | action 67 | }; 68 | let url = `${apiUrl}data/`; 69 | axios.post(url, postData).then(function (res) { 70 | if (res.status == 200) { 71 | dispatch("strategyModule/AddStrategyFromDataModule", res.data, { root: true }); 72 | } 73 | }); 74 | }, 75 | StrategySymbolChange({ dispatch }, { portfolio, strategy, action }) { 76 | let postData = { 77 | portfolio, 78 | strategy, 79 | action 80 | }; 81 | let url = `${apiUrl}data/`; 82 | axios.post(url, postData).then(function (res) { 83 | if (res.status == 200) { 84 | dispatch("strategyModule/CommitStrategy", res.data, { root: true }); 85 | } 86 | }); 87 | }, 88 | } 89 | }; -------------------------------------------------------------------------------- /client/src/store/modules/portfolio.js: -------------------------------------------------------------------------------- 1 | import { 2 | SETALLPORTFOLIOS, 3 | SETPORTFOLIO, 4 | SAVEALLPORTFOLIO, 5 | DELETEPORTFOLIE, 6 | } from "../mutationtype"; 7 | import axios from "axios"; 8 | 9 | const apiUrl = process.env.VUE_APP_APIURL || "/"; 10 | const portfolioModule = { 11 | namespaced: true, 12 | state: { 13 | Portfolios: [], 14 | Portfolio: undefined, 15 | }, 16 | getters: { 17 | Portfolios: (state) => { 18 | return state.Portfolios; 19 | }, 20 | Portfolio: (state) => { 21 | return state.Portfolio; 22 | }, 23 | }, 24 | mutations: { 25 | [SETALLPORTFOLIOS](state, _portfolios) { 26 | state.Portfolios = _portfolios; 27 | }, 28 | [SAVEALLPORTFOLIO](state, _portfolios) { 29 | state.Portfolios = _portfolios; 30 | }, 31 | [SETPORTFOLIO](state, _protfolio) { 32 | state.Portfolio = _protfolio; 33 | }, 34 | [DELETEPORTFOLIE](state, _protfolio) { 35 | var _index = state.Portfolios.findIndex((x) => x._id == _protfolio._id); 36 | if (_index > -1) { 37 | state.Portfolios.splice(_index, 1); 38 | } 39 | state.Portfolio = undefined; 40 | }, 41 | }, 42 | actions: { 43 | async GetAllPortfolios({ commit }) { 44 | const apiUrl = process.env.VUE_APP_APIURL || "/"; 45 | const response = await axios.get(apiUrl + "portfolio"); 46 | commit(SETALLPORTFOLIOS, response.data); 47 | }, 48 | async SelectPortfolioChanged({ commit }, _protfolio) { 49 | commit(SETPORTFOLIO, _protfolio); 50 | }, 51 | async GetPortfolioById({ commit }, item) { 52 | axios 53 | .post(apiUrl + "portfolio/find", { 54 | fieldName: "_id", 55 | fieldValue: item._id, 56 | }) 57 | .then(function(res) { 58 | commit(SETPORTFOLIO, res.data[0]); 59 | }); 60 | }, 61 | async SavePortfolio({ commit }, item) { 62 | item.updateui = true; 63 | axios 64 | .post(apiUrl + "portfolio/save", item) 65 | .then(function(res) { 66 | if (item.updateui) { 67 | commit(SETALLPORTFOLIOS, res.data); 68 | } else { 69 | commit(SETPORTFOLIO, res.data[0]); 70 | } 71 | }) 72 | .catch((e) => { 73 | console.error(e); 74 | }); 75 | }, 76 | async DeletePortfolio({ commit }, item) { 77 | axios.post(apiUrl + "portfolio/delete", item).then(function() { 78 | commit(DELETEPORTFOLIE, item); 79 | }); 80 | }, 81 | async SaveAllPortfolio({ commit }, item) { 82 | axios 83 | .post(apiUrl + "portfolio/saveall", item) 84 | .then(function(res) { 85 | console.log('res.data :>> ', res.data); 86 | commit(SAVEALLPORTFOLIO, res.data); 87 | }) 88 | .catch((e) => { 89 | console.error(e); 90 | }); 91 | }, 92 | }, 93 | }; 94 | 95 | export default portfolioModule; 96 | -------------------------------------------------------------------------------- /client/src/store/modules/strategy.js: -------------------------------------------------------------------------------- 1 | import { 2 | GETALLSTRATEGIES, 3 | ADDEDITSTRATEGY, 4 | DELETESTRATEGY, 5 | SETEXCHANGEDETAIL, 6 | } from "../mutationtype"; 7 | 8 | import axios from "axios"; 9 | 10 | const apiUrl = process.env.VUE_APP_APIURL || "/"; 11 | const strategyModule = { 12 | namespaced: true, 13 | state: { 14 | Strategies: [], 15 | 16 | }, 17 | getters: { 18 | Strategies: state => { 19 | return state.Strategies; 20 | }, 21 | }, 22 | mutations: { 23 | [GETALLSTRATEGIES](state, _strategies) { 24 | state.Strategies = _strategies; 25 | state.Strategies.forEach(s => { 26 | s.x0 = s.x1 = undefined; 27 | s.trades.forEach(t => { t.checked = undefined; }); 28 | 29 | }); 30 | }, 31 | [ADDEDITSTRATEGY](state, _strategy) { 32 | var foundIndex = state.Strategies.findIndex(x => x._id == _strategy._id); 33 | if (foundIndex > -1) { 34 | Object.assign(state.Strategies[foundIndex], _strategy); 35 | } else { 36 | state.Strategies.unshift(_strategy); 37 | } 38 | }, 39 | [DELETESTRATEGY](state, _strategyId) { 40 | for (let i = 0, l = state.Strategies.length; i < l; i++) { 41 | if (state.Strategies[i]._id == _strategyId) { 42 | state.Strategies.splice(i, 1); 43 | return; 44 | } 45 | } 46 | }, 47 | [SETEXCHANGEDETAIL](state, exchangeDetail) { 48 | state.ExchangeDetail = exchangeDetail 49 | }, 50 | 51 | }, 52 | actions: { 53 | GetAllStrategies: async function ({ commit }, item) { 54 | axios.post(apiUrl + "strategy/findusingportfolioid", { 55 | "fieldName": "portfolio", 56 | "fieldValue": item._id, 57 | }).then(function (res) { 58 | if (res.data) { 59 | commit(GETALLSTRATEGIES, res.data); 60 | } 61 | }); 62 | }, 63 | AddStrategy({ commit }, _pid) { 64 | var item = { 65 | name: "Strategy", 66 | description: "", 67 | symbol: "NIFTY", 68 | portfolio: _pid, 69 | lotsize: 50, 70 | strikepricestep: 50, 71 | }; 72 | axios.post(apiUrl + "strategy/save", item).then(function (res) { 73 | if (res.status == 200) { 74 | res.data.isedit = true; 75 | commit(ADDEDITSTRATEGY, res.data); 76 | } 77 | }); 78 | }, 79 | EditStrategy({ commit }, item) { 80 | axios.post(apiUrl + "strategy/save", item).then(function (res) { 81 | if (res.status == 200) { 82 | commit(ADDEDITSTRATEGY, res.data); 83 | } 84 | }); 85 | }, 86 | DeleteStrategy({ commit }, item) { 87 | axios.post(apiUrl + "strategy/delete", item).then(function (res) { 88 | if (res.status == 200) { 89 | commit(DELETESTRATEGY, item._id); 90 | } 91 | }) 92 | }, 93 | MoveStrategy({ commit }, { Strategy, PortfolioID }) { 94 | Strategy.portfolio = PortfolioID; 95 | axios.post(apiUrl + "strategy/save", Strategy).then(function (res) { 96 | if (res.status == 200) { 97 | commit(DELETESTRATEGY, res.data._id); 98 | } 99 | }); 100 | }, 101 | MergeStrategy({ commit, rootGetters }, { SourceStrategy, DestinationStrategyID }) { 102 | const allStrategies = rootGetters["strategyModule/Strategies"]; 103 | 104 | let DestinationStrategy; 105 | allStrategies.forEach(x => { 106 | if (x._id == DestinationStrategyID) { 107 | DestinationStrategy = x; 108 | } 109 | }) 110 | 111 | if (DestinationStrategy && SourceStrategy.symbol == DestinationStrategy.symbol 112 | && SourceStrategy.lotsize == DestinationStrategy.lotsize 113 | && SourceStrategy.strikepricestep == DestinationStrategy.strikepricestep) { 114 | 115 | 116 | SourceStrategy.trades.forEach(x => { x._id = undefined; }); 117 | DestinationStrategy.trades = [...DestinationStrategy.trades, ...SourceStrategy.trades]; 118 | axios.post(apiUrl + "strategy/save", DestinationStrategy).then(function () { 119 | axios.post(apiUrl + "strategy/delete", SourceStrategy).then(function (res2) { 120 | if (res2.status == 200) { 121 | commit(DELETESTRATEGY, SourceStrategy._id); 122 | } 123 | }); 124 | }); 125 | } else { 126 | console.error("Source and destination strategy not matching (Symbol/Lot size/Strike Price Step)."); 127 | } 128 | }, 129 | AddStrategyFromDataModule({ dispatch }, Strategy) { 130 | dispatch("EditStrategy", Strategy); 131 | }, 132 | CommitStrategy({ commit }, Strategy) { 133 | commit(ADDEDITSTRATEGY, Strategy); 134 | } 135 | }, 136 | }; 137 | 138 | export default strategyModule; -------------------------------------------------------------------------------- /client/src/store/modules/trade.js: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | BINDADDEDITTRADE, 4 | ADDEDITTRADE, 5 | DELETETRADE, 6 | CHANGECHECKSTATE, 7 | 8 | } from "../mutationtype"; 9 | 10 | 11 | import axios from "axios"; 12 | // const axios = require("axios"); 13 | const apiUrl = process.env.VUE_APP_APIURL || "/"; 14 | 15 | 16 | 17 | const tradeModule = { 18 | namespaced: true, 19 | state: { 20 | TradeDetail: undefined, 21 | }, 22 | getters: { 23 | TradeDetail: function (state) { 24 | return state.TradeDetail; 25 | }, 26 | }, 27 | mutations: { 28 | [BINDADDEDITTRADE](state, { _strategy, _trade }) { 29 | _strategy.trades.push(_trade); 30 | }, 31 | [ADDEDITTRADE](state, { strategies, _strategy }) { 32 | var foundIndex = strategies.findIndex(x => x._id == _strategy._id); 33 | if (foundIndex > -1) { 34 | strategies[foundIndex].trades = _strategy.trades; 35 | } 36 | }, 37 | [DELETETRADE](state, { strategies, sid, tid }) { 38 | var foundIndex = strategies.findIndex(x => x._id == sid); 39 | if (foundIndex > -1) { 40 | var strategy = strategies[foundIndex]; 41 | var tradeIndex = strategy.trades.findIndex(y => y._id == tid); 42 | if (tradeIndex > -1) { 43 | strategy.trades.splice(tradeIndex, 1); 44 | } 45 | } 46 | }, 47 | [CHANGECHECKSTATE](state, items) { 48 | var foundIndex = items.strategies.findIndex(x => x._id == items.strategy._id); 49 | if (foundIndex > -1) { 50 | items.strategies[foundIndex] = items.strategy 51 | } 52 | } 53 | }, 54 | actions: { 55 | SortTrades(context, strategy) { 56 | const compare = function (a, b) { 57 | if (a.order < b.order) { 58 | return -1; 59 | } 60 | if (a.order > b.order) { 61 | return 1; 62 | } 63 | return 0; 64 | }; 65 | return strategy.trades.sort(compare); 66 | }, 67 | BindAddEditTrade({ commit, rootGetters, dispatch }, _strategy) { 68 | if (_strategy) { 69 | const _tradeDetail = { 70 | symbol: _strategy.symbol, 71 | sid: _strategy._id, 72 | expiry: null, 73 | buyorsell: "Sell", //Buy/Sell 74 | tradetype: "Call", //Call/Put/Future 75 | quantity: 1, 76 | selectedstrike: 19500, 77 | price: 30, 78 | note: "", 79 | order: _strategy.trades.length, 80 | }; 81 | return axios.post(apiUrl + "trade/save", _tradeDetail).then(function (res) { 82 | if (res.status == 200) { 83 | var strategies = rootGetters["strategyModule/Strategies"]; 84 | var _strategy = res.data; 85 | _strategy.trades.forEach(t => { 86 | t.checked = true; 87 | }); 88 | dispatch('SortTrades', _strategy); 89 | commit(ADDEDITTRADE, { strategies, _strategy }); 90 | return _strategy; 91 | } 92 | }); 93 | } else { 94 | //commit(BINDADDEDITTRADE, null) 95 | } 96 | }, 97 | AddEditTrade({ commit, rootGetters, dispatch }, _tradeDetail) { 98 | if (_tradeDetail) { 99 | axios.post(apiUrl + "trade/save", _tradeDetail).then(function (res) { 100 | if (res.status == 200) { 101 | var strategies = rootGetters["strategyModule/Strategies"]; 102 | var _strategy = res.data; 103 | _strategy.trades.forEach(t => { 104 | t.checked = true; 105 | }); 106 | dispatch('SortTrades', _strategy); 107 | commit(ADDEDITTRADE, { strategies, _strategy }); 108 | } 109 | }); 110 | } 111 | }, 112 | DeleteTrade({ commit, rootGetters}, { sid, tid }) { 113 | if (sid && tid) { 114 | axios.post(apiUrl + "trade/delete", { tid }).then(function (res) { 115 | if (res.status == 200) { 116 | var strategies = rootGetters["strategyModule/Strategies"]; 117 | // dispatch('SortTrades', strategy); 118 | commit(DELETETRADE, { strategies, sid, tid }); 119 | } 120 | }); 121 | } 122 | }, 123 | CheckStateChanged({ commit, rootGetters, dispatch }, strategy) { 124 | var strategies = rootGetters["strategyModule/Strategies"]; 125 | dispatch('SortTrades', strategy); 126 | commit(CHANGECHECKSTATE, { strategies, strategy }); 127 | } 128 | } 129 | }; 130 | 131 | export default tradeModule; -------------------------------------------------------------------------------- /client/src/store/mutationtype.js: -------------------------------------------------------------------------------- 1 | ///Portfolio 2 | export const SETALLPORTFOLIOS = 'GetAllPortfilios'; 3 | export const SETPORTFOLIO = 'SetSelectedPortfolio'; 4 | export const SAVEALLPORTFOLIO = 'SaveAllPortfolio'; 5 | export const DELETEPORTFOLIE = 'DeletePortfolio'; 6 | //Strategy 7 | export const GETALLSTRATEGIES = 'GetAllStrategy'; 8 | export const ADDEDITSTRATEGY = 'AddEditStrategy'; 9 | export const DELETESTRATEGY = 'DeleteStrategy'; 10 | //trade 11 | export const BINDADDEDITTRADE = 'BindAddEditTrade'; 12 | export const ADDEDITTRADE = 'AddEditTrade'; 13 | export const DELETETRADE = 'DeleteTrade'; 14 | export const CHANGECHECKSTATE = 'ChangeCheckState'; 15 | //data 16 | export const SETEXCHANGEDETAIL = 'SetExchangeDetail'; 17 | export const SETCURRENTEXCHANGE = 'SetCurrentExchange'; 18 | 19 | -------------------------------------------------------------------------------- /client/src/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | 4 | 5 | .btn { 6 | @apply cursor-pointer inline-flex justify-center py-1 px-1 border border-transparent shadow font-medium rounded 7 | dark:text-gray-300 dark:bg-gray-600 dark:hover:bg-gray-500 dark:focus:ring-gray-500 8 | dark:drop-shadow-md 9 | bg-gray-200 hover:bg-gray-400 hover:text-gray-200 10 | focus:outline-none focus:ring-2 focus:ring-offset-2 ; 11 | } 12 | 13 | .btn-nonrounded { 14 | @apply cursor-pointer inline-flex justify-center py-1 px-1 border border-transparent shadow-sm font-medium 15 | dark:text-white dark:bg-gray-500 dark:hover:bg-gray-700 dark:focus:ring-gray-500 16 | bg-gray-200 hover:bg-gray-400 hover:text-gray-200 17 | focus:outline-none focus:ring-2 focus:ring-offset-2 ; 18 | } 19 | 20 | .btn i{ 21 | font-size: 12px; 22 | } 23 | 24 | .btn .lbltext{ 25 | font-size: 8px; 26 | } 27 | 28 | .font13px{ 29 | font-size: 13px; 30 | } 31 | 32 | .btn-mini{ 33 | @apply cursor-pointer text-xxs justify-center rounded border-none border border-transparent shadow-sm 34 | dark:text-white dark:bg-gray-500 dark:hover:bg-gray-700 dark:focus:ring-gray-500 35 | bg-gray-300 hover:bg-gray-400 hover:text-gray-200 36 | focus:outline-none focus:ring-2 focus:ring-offset-2; 37 | } 38 | 39 | 40 | .btn-ltp{ 41 | 42 | /* @apply cursor-pointer inline-flex justify-center py-1 px-1 border border-transparent shadow font-medium rounded 43 | dark:text-yellow-500 dark:bg-gray-600 dark:hover:bg-gray-500 dark:focus:ring-gray-500 44 | dark:drop-shadow-md 45 | bg-gray-200 hover:bg-gray-400 hover:text-yellow-600 46 | focus:outline-none focus:ring-2 focus:ring-offset-2 47 | text-xs ; */ 48 | 49 | @apply cursor-pointer text-xs p-1 text-center w-full rounded border-none border border-transparent shadow 50 | dark:bg-gray-600 dark:hover:bg-gray-500 dark:focus:ring-gray-500 text-yellow-700 dark:drop-shadow-md 51 | bg-gray-200 hover:bg-gray-400 dark:text-yellow-500 52 | focus:outline-none focus:ring-2 focus:ring-offset-2; 53 | } 54 | 55 | .btn-mini i{ 56 | font-size: 12px; 57 | } 58 | 59 | .mini-edit{ 60 | @apply w-12 px-1 py-1 rounded-md focus:outline-none text-xs bg-transparent; 61 | } 62 | 63 | .normal-edit { 64 | @apply w-28 px-1 py-1 rounded-md focus:outline-none border border-gray-300 bg-gray-400 dark:border-gray-600 bg-transparent; 65 | } 66 | 67 | .mini-edit-nonrounded { 68 | @apply w-12 px-1 py-1 focus:outline-none; 69 | } 70 | 71 | .chart-mini-edit{ 72 | @apply text-xs w-12 h-8 px-1 rounded-md text-right border border-gray-300 bg-gray-400 dark:border-gray-600 bg-transparent focus:outline-none; 73 | } 74 | 75 | .mini-checkbox{ 76 | @apply rounded border-none focus:outline-none bg-gray-300 dark:bg-gray-600 checked:text-gray-400 dark:checked:text-gray-700; 77 | } 78 | 79 | .tooltiptext{ 80 | @apply hidden absolute text-xxs w-20 bg-gray-500 text-white text-center py-1 rounded-md z-10 left-2 -ml-10 81 | bottom-7 after:top-full 82 | after:absolute after:left-2/4 after:border-4 after:border-solid 83 | transition-opacity duration-300 ; 84 | } 85 | 86 | ::-webkit-scrollbar { 87 | width: 6px; 88 | } 89 | ::-webkit-scrollbar-track { 90 | /* background-color: rgba(71, 85, 105, var(--tw-bg-opacity)); */ 91 | background-color: rgba(148, 163, 184, var(--tw-bg-opacity)); 92 | border-radius: 10px; 93 | 94 | /* background-color: rgba(255, 5, 115, var(--tw-border-opacity)); */ 95 | } 96 | 97 | ::-webkit-scrollbar-thumb { 98 | /* background-color: #d4aa70; */ 99 | background-color: rgba(71, 85, 105, var(--tw-border-opacity)); 100 | border-radius: 10px; 101 | } 102 | 103 | 104 | /*Hide number arrow*/ 105 | input[type=number]::-webkit-inner-spin-button, 106 | input[type=number]::-webkit-outer-spin-button { 107 | -webkit-appearance: none; 108 | margin: 0; 109 | } 110 | 111 | input[type=number] { 112 | -moz-appearance: textfield; 113 | } 114 | 115 | 116 | 117 | @tailwind utilities; -------------------------------------------------------------------------------- /client/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /client/src/views/PaperTrade.vue: -------------------------------------------------------------------------------- 1 | 16 | 18 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /client/src/views/PortfolioDetail.vue: -------------------------------------------------------------------------------- 1 | 118 | 200 | 201 | 218 | -------------------------------------------------------------------------------- /client/src/views/PortfolioMenu.vue: -------------------------------------------------------------------------------- 1 | 128 | 241 | 258 | -------------------------------------------------------------------------------- /client/src/views/StrategyDetail.vue: -------------------------------------------------------------------------------- 1 | 231 | 384 | 385 | 402 | 403 | -------------------------------------------------------------------------------- /client/src/views/TradeList.vue: -------------------------------------------------------------------------------- 1 | 208 | 544 | 561 | -------------------------------------------------------------------------------- /client/src/views/builder/Builder.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 31 | .dropdown:hover .dropdown-menu { 32 | display: block; 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /client/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo docker stop $(sudo docker ps -a -q -f ancestor='ptclient') 4 | 5 | -------------------------------------------------------------------------------- /client/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const colors = require('tailwindcss/colors'); 2 | module.exports = { 3 | mode: 'jit', 4 | purge: [ 5 | './index.html', 6 | 'public/index.html', 7 | './src/**/*.vue', 8 | './src/**/*.js', 9 | './src/**/*.{vue,js,ts,jsx,tsx}', 10 | ], 11 | darkMode: 'class', // or 'media' or 'class' 12 | theme: { 13 | colors: { 14 | // Build your palette here 15 | transparent: 'transparent', 16 | current: 'currentColor', 17 | gray: colors.blueGray, 18 | 19 | // bluegray: colors.blueGray, 20 | coolgray: colors.gray, 21 | truegray: colors.neutral, 22 | warmgray: colors.stone, 23 | 24 | red: colors.red, 25 | 26 | yellow: colors.yellow, 27 | green: colors.green, 28 | white: colors.white, 29 | orange: colors.orange, 30 | cyan : colors.cyan, 31 | blue: colors.blue, 32 | //pink: colors.pink, 33 | 34 | }, 35 | fontSize: { 36 | 'xxs': '.6rem', 37 | 'xs': '.75rem', 38 | 'sm': '.875rem', 39 | 'tiny': '.875rem', 40 | 'base': '1rem', 41 | 'lg': '1.125rem', 42 | 'xl': '1.25rem', 43 | '2xl': '1.5rem', 44 | '3xl': '1.875rem', 45 | '4xl': '2.25rem', 46 | '5xl': '3rem', 47 | '6xl': '4rem', 48 | '7xl': '5rem', 49 | }, 50 | 51 | // borderWidth: { 52 | // DEFAULT: '1px', 53 | // '0': '0', 54 | // '2': '2px', 55 | // '3': '3px', 56 | // '4': '4px', 57 | // '6': '6px', 58 | // '8': '8px', 59 | // } 60 | }, 61 | variants: { 62 | extend: {}, 63 | }, 64 | plugins: [ 65 | require('@tailwindcss/forms'), 66 | ], 67 | } 68 | -------------------------------------------------------------------------------- /client/vue.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | outputDir: path.resolve(__dirname, "./dist"), 6 | devServer: { 7 | https: false, 8 | useLocalIp: false, 9 | }, 10 | chainWebpack: config => { 11 | config.plugin('html') 12 | .tap(args => { 13 | args[0].title = "Papertrade"; 14 | return args; 15 | }); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /common/logs.js: -------------------------------------------------------------------------------- 1 | const utility = require("./utility"); 2 | 3 | module.exports = new function () { 4 | this.log = (...message) => { 5 | this.info(message); 6 | } 7 | this.info = (...message) => { 8 | console.log(utility.formatDate(), 'INFO:', message.join(' ')); 9 | } 10 | this.warn = (...message) => { 11 | // let isString = message => typeof message === 'string' || message instanceof String; 12 | // console.warn(isString()); 13 | if (message.length == 1) { 14 | console.warn(utility.formatDate(), 'WARNING:', message); 15 | } else { 16 | console.warn(utility.formatDate(), 'WARNING:', message.join(' ')); 17 | } 18 | } 19 | this.error = (...message) => { 20 | console.error(utility.formatDate(), 'ERROR:', message.join(' ')); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /common/utility.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | formatDate: () => { 3 | //// AI Generated 4 | var date = new Date(); 5 | const year = date.getFullYear(); 6 | const month = module.exports.getMonthName(date.getMonth()); 7 | const day = String(date.getDate()).padStart(2, '0'); 8 | const hours = String(date.getHours()).padStart(2, '0'); 9 | const minutes = String(date.getMinutes()).padStart(2, '0'); 10 | const seconds = String(date.getSeconds()).padStart(2, '0'); 11 | const ms = String(date.getMilliseconds()).padStart(3, '0'); 12 | 13 | return `${day}-${month}-${year} ${hours}:${minutes}:${seconds}.${ms}`; 14 | }, 15 | 16 | getMonthName: (monthIndex) => { 17 | //// AI Generated 18 | const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 19 | return monthNames[monthIndex]; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: papertradeclient 5 | namespace: default 6 | labels: 7 | app: papertradeclient 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: papertradeclient 12 | replicas: 3 13 | strategy: 14 | rollingUpdate: 15 | maxSurge: 25% 16 | maxUnavailable: 25% 17 | type: RollingUpdate 18 | template: 19 | metadata: 20 | labels: 21 | app: papertradeclient 22 | spec: 23 | # initContainers: 24 | # Init containers are exactly like regular containers, except: 25 | # - Init containers always run to completion. 26 | # - Each init container must complete successfully before the next one starts. 27 | containers: 28 | - name: papertradeclient 29 | image: ubuntu 30 | resources: 31 | requests: 32 | cpu: 500m 33 | memory: 500Mi 34 | limits: 35 | cpu: 500m 36 | memory: 500Mi 37 | ports: 38 | - containerPort: 8080 39 | name: papertradeclient 40 | volumeMounts: 41 | - name: localtime 42 | mountPath: /etc/localtime 43 | volumes: 44 | - name: localtime 45 | hostPath: 46 | path: /usr/share/zoneinfo/Asia/Shanghai 47 | restartPolicy: Always -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | client: 4 | build: 5 | context: ./client 6 | dockerfile: ./Dockerfile 7 | image: pt-client 8 | ports: 9 | - 8080:8080 10 | 11 | server: 12 | build: 13 | context: ./server 14 | dockerfile: ./Dockerfile 15 | image: pt-server 16 | ports: 17 | - 9090:9090 18 | # mongo: 19 | # image: mongo 20 | # restart: always 21 | # environment: 22 | # MONGO_INITDB_ROOT_USERNAME: root 23 | # MONGO_INITDB_ROOT_PASSWORD: example 24 | 25 | # mongo-express: 26 | # image: mongo-express 27 | # restart: always 28 | # ports: 29 | # - 8081:8081 30 | # environment: 31 | # ME_CONFIG_MONGODB_ADMINUSERNAME: root 32 | # ME_CONFIG_MONGODB_ADMINPASSWORD: example 33 | # ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/ 34 | # ME_CONFIG_BASICAUTH: false 35 | 36 | 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nse-papertrade", 3 | "version": "1.0.0", 4 | "description": "Do papertrade with out losing your capital. Inspired by Sensibull and Tradingview paper trades.", 5 | "main": "index.js", 6 | "postinstall": "(cd server && npm install); (cd client && npm install)", 7 | "scripts": { 8 | "prebuild" : "ls .", 9 | "dev": "npm run dev --prefix ./client & npm run dev --prefix ./server", 10 | "echo": "echo \"use `npm run dev` from both client and server folder \" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/anandav/NSE-PaperTrade.git" 15 | }, 16 | "author": "Anand.AV", 17 | "license": "UNLICENSED", 18 | "bugs": { 19 | "url": "https://github.com/anandav/NSE-PaperTrade/issues" 20 | }, 21 | "homepage": "https://github.com/anandav/NSE-PaperTrade#readme", 22 | "devDependencies": { 23 | "eslint": "^8.30.0", 24 | "eslint-plugin-vue": "^9.8.0" 25 | }, 26 | "dependencies": { 27 | "babel-eslint": "^10.1.0" 28 | }, 29 | "eslintConfig": { 30 | "root": true, 31 | "env": { 32 | "node": true 33 | }, 34 | "extends": [ 35 | "plugin:vue/essential", 36 | "eslint:recommended" 37 | ], 38 | "parserOptions": { 39 | "parser": "babel-eslint" 40 | }, 41 | "rules": {} 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /server/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /server/.env.sample: -------------------------------------------------------------------------------- 1 | ## Mongodb Atlas 2 | ## API is tested in mongodb atlas and its works fine if its slow/not connecting make sure to Allow All IP 0.0.0.0/0 under Network Access 3 | ## replace placeholder with actual conectionstring. 4 | ## Mongo db local instance 5 | 6 | #DBCONNECTIONSTRING=DBCONNECTIONSTRING=mongodb+srv:// 7 | ## OR 8 | #DBCONNECTIONSTRING=mongodb:// 9 | 10 | ##IS DEMO mode, wont allow to Create/Update/Delete operation 11 | ENABLE_DEMO=false 12 | ##Enable data api 13 | ENABLE_DATAAPI=false 14 | 15 | #Cache Duration in seconds 16 | CACHE_DURATION=10 17 | 18 | #NSE Endpoints 19 | NSE_INDICES_LIST_API=HTTPS://NSE_API_ENDPOINT_PLACEHOLDER 20 | NSE_INDICES_FUTURES_API=HTTPS://NSE_API_ENDPOINT_PLACEHOLDER 21 | NSE_INDICES_OPTIONS_API=HTTPS://NSE_API_ENDPOINT_PLACEHOLDER 22 | NSE_MKT_LOTS=HTTPS://NSE_API_ENDPOINT_PLACEHOLDER 23 | NSE_EQUITIES_API=HTTPS://NSE_API_ENDPOINT_PLACEHOLDER 24 | NSE_EQUITIES_FUTURES_LIST_API=HTTPS://NSE_API_ENDPOINT_PLACEHOLDER 25 | NSE_EQUITIES_FUTURES_API=HTTPS://NSE_API_ENDPOINT_PLACEHOLDER 26 | NSE_EQUITIES_OPTIONS_API=HTTPS://NSE_API_ENDPOINT_PLACEHOLDER 27 | NSE_EQUITIES_META_INFO_API=HTTPS://NSE_API_ENDPOINT_PLACEHOLDER 28 | NSE_CURRENCY_FUTURES_LIST_API=HTTPS://NSE_API_ENDPOINT_PLACEHOLDER 29 | NSE_CURRENCY_FUTURES_LIST_API2=HTTPS://NSE_API_ENDPOINT_PLACEHOLDER 30 | NSE_CURRENCY_OPTIONS_API=HTTPS://NSE_API_ENDPOINT_PLACEHOLDER 31 | #CBOE Endpoints 32 | CBOE_LIST_API=HTTPS://CBOE_API_ENDPOINT_PLACEHOLDER 33 | CBOE_EQUITIES_API=HTTPS://CBOE_API_ENDPOINT_PLACEHOLDER 34 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | public 2 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine AS buildps 2 | WORKDIR /server 3 | COPY . . 4 | RUN npm install 5 | RUN npm run build 6 | 7 | FROM node:lts-alpine 8 | 9 | COPY --from=buildps /server/index.html /home/server/index.html 10 | COPY --from=buildps /server/.env /home/server/.env 11 | COPY --from=buildps /server/node_modules /home/server/node_modules 12 | COPY --from=buildps /server/dist/ /home/server 13 | EXPOSE 9090 14 | CMD [ "node", "--env-file=/home/server/.env", "/home/server/index.js" ] 15 | -------------------------------------------------------------------------------- /server/Dockerfile.backup: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | WORKDIR /server 3 | COPY . . 4 | RUN npm --prefix ./server install 5 | RUN npm --prefix ./server run build 6 | RUN cp -r ./server/dist/ /home/server 7 | RUN cp -r ./server/node_modules /home/server 8 | RUN cp ./server/.env /home/server/ 9 | EXPOSE 9090 10 | CMD [ "node", "--env-file=/home/server/.env", "/home/server/index.js" ] 11 | -------------------------------------------------------------------------------- /server/buildandrun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build -t ptserver . 4 | docker run -d -p 9090:9090 ptserver 5 | -------------------------------------------------------------------------------- /server/index.html: -------------------------------------------------------------------------------- 1 | Hello World!!! -------------------------------------------------------------------------------- /server/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: ptserver-api2 5 | namespace: default 6 | labels: 7 | app: ptserver-api2 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: ptserver-api2 12 | replicas: 4 13 | strategy: 14 | rollingUpdate: 15 | maxSurge: 25% 16 | maxUnavailable: 25% 17 | type: RollingUpdate 18 | template: 19 | metadata: 20 | labels: 21 | app: ptserver-api2 22 | spec: 23 | containers: 24 | - name: ptserver-api2 25 | image: k8sclusterdevind01registry.azurecr.io/ptserver:002 26 | resources: 27 | requests: 28 | cpu: 100m 29 | memory: 100Mi 30 | limits: 31 | cpu: 100m 32 | memory: 100Mi 33 | ports: 34 | - containerPort: 9090 35 | restartPolicy: Always -------------------------------------------------------------------------------- /server/k8s/loadBalancer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: ptserver-api2-frmfile 5 | namespace: default 6 | spec: 7 | selector: 8 | app: ptserver-api2 9 | type: LoadBalancer 10 | sessionAffinity: None 11 | sessionAffinityConfig: 12 | clientIP: 13 | timeoutSeconds: 10800 14 | ports: 15 | - name: ptserver-api2 16 | protocol: TCP 17 | port: 90 18 | targetPort: 9090 19 | 20 | 21 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "0.1.2", 4 | "description": "Papper Trade Service using Express.js.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev-debug": "DEBUG=express:* nodemon ./src/index.js", 8 | "dev": "clear && node ./src/index.js --trace-deprecation", 9 | "build": "rm -rf dist && webpack", 10 | "start": "node ./src/index.js" 11 | }, 12 | "author": "Anand.AV", 13 | "license": "UNLICENSED", 14 | "dependencies": { 15 | "axios": "^1.0.0", 16 | "body-parser": "^1.19.0", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.4.7", 19 | "ejs": "^3.1.7", 20 | "express": "^4.17.1", 21 | "helmet": "^8.0.0", 22 | "jmespath": "^0.16.0", 23 | "mongodb": "^6.15.0", 24 | "mongoose": "^8.12.2" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.18.6", 28 | "@babel/preset-env": "^7.18.6", 29 | "babel-loader": "^10.0.0", 30 | "html-webpack-plugin": "^5.5.0", 31 | "webpack": "^5.82.0", 32 | "webpack-cli": "^6.0.0", 33 | "webpack-node-externals": "^3.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /server/remove.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo docker stop $(sudo docker ps -a -q --filter ancestor='ptserver') 4 | sudo docker rm $(sudo docker ps -a -q --filter ancestor='ptserver') 5 | sudo docker rmi 'ptserver' 6 | 7 | -------------------------------------------------------------------------------- /server/src/common/logs.js: -------------------------------------------------------------------------------- 1 | const utility = require("./utility"); 2 | 3 | module.exports = new function () { 4 | this.log = (...message) => { 5 | this.info(message); 6 | } 7 | this.info = (...message) => { 8 | console.log(utility.formatDate(), 'INFO:', message.join(' ')); 9 | } 10 | this.warn = (...message) => { 11 | // let isString = message => typeof message === 'string' || message instanceof String; 12 | // console.warn(isString()); 13 | if (message.length == 1) { 14 | console.warn(utility.formatDate(), 'WARNING:', message); 15 | } else { 16 | console.warn(utility.formatDate(), 'WARNING:', message.join(' ')); 17 | } 18 | } 19 | this.error = (...message) => { 20 | console.error(utility.formatDate(), 'ERROR:', message.join(' ')); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /server/src/common/utility.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | formatDate: () => { 3 | //// AI Generated 4 | var date = new Date(); 5 | const year = date.getFullYear(); 6 | const month = module.exports.getMonthName(date.getMonth()); 7 | const day = String(date.getDate()).padStart(2, '0'); 8 | const hours = String(date.getHours()).padStart(2, '0'); 9 | const minutes = String(date.getMinutes()).padStart(2, '0'); 10 | const seconds = String(date.getSeconds()).padStart(2, '0'); 11 | const ms = String(date.getMilliseconds()).padStart(3, '0'); 12 | 13 | return `${day}-${month}-${year} ${hours}:${minutes}:${seconds}.${ms}`; 14 | }, 15 | 16 | getMonthName: (monthIndex) => { 17 | //// AI Generated 18 | const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 19 | return monthNames[monthIndex]; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /server/src/controller/portfoliocotroller.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const portfolicontroller = express.Router(); 3 | const Portfolio = require("../models/portfolio"); 4 | const commonUtility = require("../models/commonUtility"); 5 | const portfolio = require("../models/portfolio"); 6 | 7 | require("dotenv/config"); 8 | 9 | portfolicontroller.get("/", async (req, res) => { 10 | const data = await Portfolio.find().sort({ modifiedon: -1 }); 11 | res.json(data); 12 | }); 13 | 14 | portfolicontroller.post("/find", async (req, res) => { 15 | let { fieldName, fieldValue } = req.body; 16 | let result = {}; 17 | if (fieldName && fieldValue) { 18 | result = await Portfolio.find({ [fieldName]: fieldValue }).sort({ 19 | order: -1 20 | }); 21 | } else { 22 | result = await Portfolio.find().sort({ order: -1 }); 23 | } 24 | res.send(result); 25 | }); 26 | 27 | portfolicontroller.post("/save", async (req, res) => { 28 | if (process.env.ENABLE_DEMO == "false") { 29 | const { _id, name, exchange, openingbalance, description, createdon, updateui } = req.body; 30 | 31 | let _portfolioObject = {}; 32 | if (_id) { 33 | _portfolioObject = await commonUtility.GetPortfolioById(_id); 34 | _portfolioObject.name = name; 35 | _portfolioObject.exchange = exchange; 36 | _portfolioObject.openingbalance = openingbalance; 37 | _portfolioObject.description = description; 38 | _portfolioObject.modifiedon = new Date(); 39 | 40 | } else { 41 | _portfolioObject = new Portfolio({ 42 | name, 43 | exchange, 44 | description, 45 | createdon, 46 | }); 47 | } 48 | try { 49 | const result = await _portfolioObject.save(); 50 | if (updateui) { 51 | const _allportfolio = await Portfolio.find(); 52 | res.send(_allportfolio); 53 | } else { 54 | res.send(result); 55 | } 56 | } catch (err) { 57 | console.error(err); 58 | res.send(err); 59 | } 60 | } else { 61 | res.status(401); 62 | res.send({ error: "Cant edit portfolio on demo mode." }); 63 | } 64 | }); 65 | portfolicontroller.post("/saveall", async (req, res) => { 66 | let _portfolios = req.body; 67 | let _portfolioObject = {}; 68 | _portfolios.forEach(async (item) => { 69 | _portfolioObject = await commonUtility.GetPortfolioById(item._id); 70 | _portfolioObject.name = item.name; 71 | _portfolioObject.exchange = item.exchange; 72 | _portfolioObject.openingbalance = item.openingbalance; 73 | _portfolioObject.description = item.description; 74 | _portfolioObject.order = item.order; 75 | _portfolioObject.modifiedon = new Date(); 76 | const result = await _portfolioObject.save(); 77 | }); 78 | res.send(await findPortfolio()); 79 | }); 80 | 81 | portfolicontroller.post("/delete", async (req, res) => { 82 | let pid = req.body._id; 83 | if (pid) { 84 | if (process.env.ENABLE_DEMO == "false") { 85 | commonUtility.DeleteStrategyUsingPortfolioID(pid); 86 | Portfolio.deleteOne({ _id: pid }, (err, doc) => { 87 | res.send(doc); 88 | }); 89 | } else { 90 | res.status(401); 91 | res.send({ error: "Cant delete portfolio on demo mode." }); 92 | } 93 | } 94 | }); 95 | 96 | async function findPortfolio() { 97 | return await Portfolio.find().sort({ order: 1 }); 98 | } 99 | 100 | module.exports = portfolicontroller; 101 | -------------------------------------------------------------------------------- /server/src/controller/strategycontroller.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const mongoose = require('mongoose'); 3 | const strategycontoller = express.Router(); 4 | const commUtility = require("../models/commonUtility"); 5 | const portfolio = require("../models/portfolio"); 6 | const Strategy = require("../models/strategy"); 7 | const trade = require("../models/trade"); 8 | 9 | strategycontoller.post("/findusingportfolioid", async (req, res) => { 10 | let { fieldName, fieldValue } = req.body; 11 | let result = {}; 12 | if (fieldName && fieldValue) { 13 | result = await Strategy.aggregate( 14 | [ 15 | { $match: { [fieldName]: new mongoose.Types.ObjectId(`${fieldValue}`) } }, 16 | { $sort: { "createdon": -1, 'trades.order': -1 } } 17 | ] 18 | ); 19 | res.json(result); 20 | } else { 21 | result = await Strategy.find({}); 22 | } 23 | }); 24 | 25 | strategycontoller.post("/save", async (req, res) => { 26 | if (process.env.ENABLE_DEMO == 'false') { 27 | const { _id, portfolio, name, description, symbol, symboltype, lotsize, expiry, strikepricestep, isarchive, hidechart,ismultiplesymbol, trades, createdon } = req.body; 28 | let _data = { 29 | _id, 30 | name, 31 | description, 32 | symbol, 33 | symboltype, 34 | lotsize, 35 | expiry, 36 | strikepricestep, 37 | isarchive, 38 | hidechart, 39 | ismultiplesymbol, 40 | portfolio, 41 | createdon, 42 | modifiedon: new Date(), 43 | }; 44 | 45 | if (trades) { 46 | _data.trades = trades; 47 | } 48 | 49 | if (_id) { 50 | let _strategyObject = await Strategy.updateOne( 51 | { _id: _id }, 52 | { 53 | $set: _data, 54 | } 55 | ); 56 | res.send(_data); 57 | } else { 58 | const strategy = new Strategy(_data); 59 | try { 60 | _data.createdon = new Date(); 61 | const result = await strategy.save(); 62 | res.send(result); 63 | } catch (err) { 64 | console.error(err); 65 | } 66 | } 67 | } else { 68 | res.status(401); 69 | res.send({ "error": "Cant edit strategy on demo mode." }) 70 | } 71 | 72 | 73 | }); 74 | 75 | strategycontoller.post("/delete", async (req, res) => { 76 | let { _id } = req.body; 77 | DeleteStrategy(_id, res); 78 | }); 79 | 80 | 81 | function DeleteStrategy(_id, res) { 82 | if (_id) { 83 | if (process.env.ENABLE_DEMO == 'false') { 84 | Strategy.deleteOne({ _id: _id }, (err, doc) => { 85 | res.json(doc); 86 | }); 87 | } else { 88 | 89 | res.status(401); 90 | res.json({ "error": "Cant delete strategy on demo mode." }); 91 | } 92 | } 93 | }; 94 | 95 | 96 | module.exports = strategycontoller; 97 | -------------------------------------------------------------------------------- /server/src/controller/tradecontroller.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const tradeController = express.Router(); 3 | const Strategy = require("../models/strategy"); 4 | const Trade = require("../models/trade"); 5 | const commonUtility = require("../models/commonUtility"); 6 | 7 | tradeController.post("/save", async (req, res) => { 8 | if (process.env.ENABLE_DEMO == 'false') { 9 | const { 10 | sid, 11 | _id, 12 | symbol, 13 | lotsize, 14 | buyorsell, 15 | tradetype, 16 | quantity, 17 | selectedstrike, 18 | price, 19 | lasttradedprice, 20 | isexit, 21 | partnerid, 22 | note, 23 | group, 24 | strikepricemin, 25 | strikepricemax, 26 | strikepricestep, 27 | order, 28 | color, 29 | createdon, 30 | modifiedon, 31 | 32 | } = req.body; 33 | let _trade = new Trade({ 34 | symbol, 35 | lotsize, 36 | buyorsell, 37 | tradetype, 38 | quantity, 39 | selectedstrike, 40 | price, 41 | lasttradedprice, 42 | isexit, 43 | partnerid, 44 | note, 45 | group, 46 | strikepricemin, 47 | strikepricemax, 48 | strikepricestep, 49 | order, 50 | color, 51 | createdon: new Date(), 52 | modifiedon: new Date(), 53 | }); 54 | 55 | 56 | if (_id) { 57 | _trade._id = _id; 58 | await Strategy.updateOne( 59 | { "trades._id": _id }, 60 | { $set: { "trades.$": _trade } } 61 | ); 62 | 63 | commonUtility.GetTradeById(_id).then((_result) => { 64 | res.json(_result); 65 | }); 66 | } else if (sid && !_id) { 67 | let _strategyObject = await commonUtility.GetStrategyById(sid); 68 | if (_strategyObject) { 69 | _strategyObject.trades.push(_trade); 70 | _strategyObject.save(function (_error, doc) { 71 | res.json(doc); 72 | }); 73 | } else { 74 | res.json({ "error_msg": "Strategy not found." }); 75 | } 76 | } 77 | } else { 78 | res.status(401); 79 | res.send({ "error": "Cant edit trade on demo mode." }) 80 | } 81 | }); 82 | 83 | tradeController.post("/delete", async (req, res) => { 84 | let { tid } = req.body; 85 | if (tid) { 86 | if (process.env.ENABLE_DEMO == 'false') { 87 | const result = await Strategy.updateOne({ "trades._id": tid }, 88 | { $pull: { "trades": { _id: tid } } }, 89 | { new: true }, function (err) { 90 | if (err) { console.error(err); } 91 | } 92 | ); 93 | res.json(result); 94 | } else { 95 | res.status(401); 96 | res.json({ "error": "Cant delete trade on demo mode." }) 97 | } 98 | } 99 | }); 100 | 101 | module.exports = tradeController; 102 | -------------------------------------------------------------------------------- /server/src/dataprovidercontroller/cboe.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios").default; 2 | const jmespath = require("jmespath"); 3 | 4 | require("dotenv/config"); 5 | let lastupdated = null; 6 | let symbolList = null; 7 | 8 | module.exports = { 9 | Maps: { 10 | getAllTradeType: "trades[?!isexit].tradetype", 11 | }, 12 | Get: async function (portfolio, startegy, action) { 13 | if (action == "init") { 14 | if (!lastupdated) { 15 | lastupdated = new Date(); 16 | } else { 17 | lastupdated = new Date(lastupdated); 18 | } 19 | let now = Date.now(); 20 | let totalHourDiff = Math.abs(now - lastupdated) / 36e5; 21 | if (!symbolList || totalHourDiff > 24) { 22 | let _inputData = await this.GetSymbolBook(); 23 | symbolList = this.getObject(_inputData, "data[*]"); 24 | return symbolList; 25 | } else { 26 | console.log( 27 | "Loading from memory Dataset :>> last updated ", 28 | lastupdated, 29 | " Total Hours: ", 30 | totalHourDiff 31 | ); 32 | } 33 | } 34 | }, 35 | GetSymbolBook: async function () { 36 | const url = process.env.CBOE_LIST_API; 37 | return this.getData(url); 38 | }, 39 | getObject: function (inputData, selector) { 40 | return jmespath.search(inputData, selector); 41 | }, 42 | getData: async function (url) { 43 | ///Ref: https://stackoverflow.com/questions/67864408/how-to-return-server-response-from-axios 44 | try { 45 | const responce = await axios.get(url); 46 | return responce.data; 47 | } catch (e) { 48 | return null; 49 | //return e.reponce.data 50 | } 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /server/src/dataprovidercontroller/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const dataprovider = express.Router(); 3 | const nse = require("./nse"); 4 | const cboe = require("./cboe"); 5 | 6 | dataprovider.post("/", async (req, res) => { 7 | var result = await get(req); 8 | res.json(result); 9 | }); 10 | 11 | 12 | async function getExchangeDetail() { 13 | const Action = { "list": 1, "details": 2, }; 14 | // "Future": 4, "Option": 8 15 | const SymbolTypes = [ 16 | { "name": "Equity", "value": 1024 }, 17 | { "name": "Indices", "value": 2048 }, 18 | { "name": "Currency", "value": 4096 }, 19 | { "name": "Crypto", "value": 8192 }, 20 | { "name": "Commodity", "value": 16384 }]; 21 | 22 | const result = { 23 | Action, 24 | SymbolTypes, 25 | "Symbols": {}, 26 | "Exchanges": [ 27 | { "name" : "NSE" , "isactive" : true , "type" : "exchange" , "description" : "National Stock Exchange." , "segmentendpoints": 22569 }, 28 | { "name" : "CBOE" , "isactive" : true , "type" : "exchange" , "description" : "Chicago Board Options Exchange." , "segmentendpoints": 2051 }, 29 | { "name" : "BSE" , "isactive" : false , "type" : "exchange" , "description" : "Bombay Stock Exchange." , "segmentendpoints": 0 }, 30 | { "name" : "MCX" , "isactive" : false , "type" : "exchange" , "description" : "Multi Commodity Exchange." , "segmentendpoints": 0 }, 31 | { "name" : "WAZIRX" , "isactive" : false , "type" : "exchange" , "description" : "Crypto Exchange." , "segmentendpoints": 16387 }, 32 | { "name" : "KOTAK" , "isactive" : true , "type" : "broker" , "description" : "Kotak Securities." , "segmentendpoints": 22569 }] 33 | }; 34 | return result; 35 | } 36 | 37 | 38 | async function get(req) { 39 | var result = {}; 40 | var body = req.body; 41 | var portfolio = body.portfolio; 42 | var startegy = body.strategy 43 | var action = body.action; 44 | var initResult = {}; 45 | if (action == "init") { 46 | result.ExchangeDetail = await getExchangeDetail(); 47 | if(portfolio?.exchange?.toLowerCase() == "nse"){ 48 | result.Symbols = await nse.Get(portfolio, startegy, action); 49 | }else if(portfolio?.exchange?.toLowerCase() == "cboe"){ 50 | result.Symbols = await cboe.Get(portfolio, startegy, action) 51 | } 52 | return result; 53 | } 54 | if (portfolio?.exchange?.toLowerCase() == "nse") { 55 | result = await nse.Get(portfolio, startegy, action); 56 | }else if(portfolio?.exchange?.toLowerCase() == "cboe") { 57 | // Write the code for CBOE here. 58 | } 59 | else { 60 | result = { "error": "Not a valid request." }; 61 | } 62 | return result; 63 | } 64 | 65 | module.exports = dataprovider; -------------------------------------------------------------------------------- /server/src/dataprovidercontroller/nse.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios").default; 2 | const jmespath = require("jmespath"); 3 | const logger = require("../common/logs"); 4 | 5 | require("dotenv/config"); 6 | let currencyFutList = null; 7 | let equityFutList = null; 8 | let indicesFutList = null; 9 | let mktLotsList = null; 10 | let lastupdated = null; 11 | let nseCookies = null; 12 | let baseUrl = "https://www.nseindia.com/"; 13 | let headers = { 14 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', 15 | 'accept-language': 'en,gu;q=0.9,hi;q=0.8', 'accept-encoding': 'gzip, deflate, br' 16 | }; 17 | let Instance = axios.create({ 18 | baseURL: baseUrl, 19 | headers: headers, 20 | cookie: nseCookies ?? "" 21 | }); 22 | 23 | 24 | 25 | module.exports = { 26 | Maps: { 27 | getAllTradeType: "trades[?!isexit].tradetype", 28 | }, 29 | Get: async function (portfolio, startegy, action) { 30 | this.setCacheObject(`"action"`, action); 31 | if (action == "init") { 32 | debugger; 33 | if (!lastupdated) { 34 | lastupdated = new Date(); 35 | } else { 36 | lastupdated = new Date(lastupdated); 37 | } 38 | let now = Date.now(); 39 | let totalHourDiff = Math.abs(now - lastupdated) / 36e5; 40 | 41 | //|| totalHourDiff > 24 42 | if ( 43 | !equityFutList || 44 | !indicesFutList || 45 | !currencyFutList || 46 | !mktLotsList 47 | ) { 48 | if (!indicesFutList) { 49 | logger.info(`Getting Indices List`); 50 | indicesFutList = await this.GetIndicesList(); 51 | } 52 | if (!currencyFutList) { 53 | logger.info(`Getting Currency Futures`); 54 | currencyFutList = await this.GetCurrencyFuture(); 55 | } 56 | if (!equityFutList) { 57 | logger.info(`Getting Equity Futures`); 58 | equityFutList = await this.GetEquitiesFuturesList(); 59 | logger.info(`Got Equity Futures`); 60 | } 61 | if (!mktLotsList) { 62 | logger.info(`Getting Lot size`); 63 | let csv = await this.GetMRKTLot(); 64 | mktLotsList = this.csvJSON(csv); 65 | logger.info(`Got Lot size `) 66 | } 67 | lastupdated = now; 68 | } 69 | let result = []; 70 | const indices = [ 71 | { name: "NIFTY", lotsize: 50 }, 72 | { name: "BANKNIFTY", lotsize: 25 }, 73 | { name: "FINNIFTY", lotsize: 40 }, 74 | ]; 75 | indices.forEach((item) => { 76 | result.push({ ...item, symboltype: "Indices", istradeble: true }); 77 | }); 78 | if (equityFutList) { 79 | equityFutList.forEach((item) => { 80 | result.push({ 81 | name: item, 82 | lotsize: 0, 83 | symboltype: "Equity", 84 | istradeble: true, 85 | }); 86 | }); 87 | } 88 | if (currencyFutList) { 89 | currencyFutList.data.forEach((item) => { 90 | result.push({ 91 | name: `${item.unit}INR`, 92 | lotsize: 1000, 93 | symboltype: "Currency", 94 | istradeble: true, 95 | }); 96 | }); 97 | } 98 | return result; 99 | } else { 100 | let symbol = startegy.symbol; 101 | let symboltype = startegy.symboltype?.toLowerCase(); 102 | logger.info("symboltype ::>>", symboltype); 103 | let allTradeType = this.getTradeTypes(startegy); 104 | logger.info("Action:>>", action); 105 | if (allTradeType) { 106 | logger.info("All Trade Type is Present") 107 | } else { 108 | logger.error("All Trade Type is null") 109 | } 110 | let hasEquity = allTradeType.includes("Equity") || allTradeType.includes("Stock"); 111 | let hasFutures = allTradeType.includes("Future"); 112 | let hasOptions = allTradeType.includes("Call") || allTradeType.includes("Put"); 113 | 114 | if (symboltype == "equity") { 115 | let equityData = null; 116 | if (hasEquity) { 117 | equityData = await this.GetEquitiyDetail(symbol); 118 | startegy = this.bindEquityData(startegy, equityData, action); 119 | } 120 | if (hasFutures) { 121 | equityData = await this.GetEquityFuture(symbol); 122 | logger.info("equityData Futures:>> ", equityData); 123 | } 124 | if (hasOptions) { 125 | equityData = await this.GetEquityOptionChain(symbol); 126 | logger.info("equityData Option:>> ", equityData); 127 | startegy = this.bindOptionData(startegy, equityData, action); 128 | } 129 | return startegy; 130 | } 131 | 132 | 133 | if (symboltype == "indices") { 134 | 135 | let nseData = null; 136 | if (hasFutures) { 137 | let futData = await this.GetIndicesFutures(symbol); 138 | } 139 | if (hasOptions) { 140 | nseData = await this.GetIndicesOptionChain(symbol); 141 | 142 | // logger.info("Indices Option:>> ", nseData); 143 | startegy = this.bindOptionData(startegy, nseData, action); 144 | if (action == "getexpiries") { 145 | startegy = this.bindExpiriesData(startegy, nseData); 146 | } 147 | } 148 | return startegy; 149 | } 150 | 151 | if (symboltype == "currency") { 152 | let currencyData = null; 153 | if (hasFutures) { 154 | currencyData = await this.GetCurrencyFuture(symbol); 155 | } 156 | if (hasOptions) { 157 | let nseData = await this.GetCurrencyOptionChain(symbol); 158 | startegy = this.bindOptionData(startegy, nseData, action); 159 | } 160 | return startegy; 161 | } 162 | } 163 | }, 164 | GetEquitiyDetail: async function (equity) { 165 | const url = process.env.NSE_EQUITIES_API.replace("PARAMETER", equity); 166 | return this.getData(url); 167 | }, 168 | GetIndicesList: async function () { 169 | const url = process.env.NSE_INDICES_LIST_API; 170 | return this.getData(url); 171 | }, 172 | GetIndicesFutures: async function (indices) { 173 | const url = process.env.NSE_INDICES_FUTURES_API.replace( 174 | "PARAMETER", 175 | indices 176 | ); 177 | return this.getData(url); 178 | }, 179 | GetIndicesOptionChain: async function (indices) { 180 | const url = process.env.NSE_INDICES_OPTIONS_API.replace( 181 | "PARAMETER", 182 | indices 183 | ); 184 | return this.getData(url); 185 | }, 186 | GetEquitiesFuturesList: async function () { 187 | var data = this.getData(process.env.NSE_EQUITIES_FUTURES_LIST_API); 188 | return data; 189 | }, 190 | GetEquityFuture: async function (equity) { 191 | const url = process.env.NSE_EQUITIES_FUTURES_API.replace( 192 | "PARAMETER", 193 | equity 194 | ); 195 | 196 | return this.getData(url); 197 | }, 198 | GetEquityOptionChain: async function (equity) { 199 | const url = process.env.NSE_EQUITIES_OPTIONS_API.replace( 200 | "PARAMETER", 201 | equity 202 | ); 203 | return this.getData(url); 204 | }, 205 | GetCurrencyFuture: async function () { 206 | const url = process.env.NSE_CURRENCY_FUTURES_LIST_API2; 207 | return this.getData(url); 208 | }, 209 | GetMRKTLot: async function () { 210 | const url = process.env.NSE_MKT_LOTS; 211 | return this.getData(url); 212 | }, 213 | GetCurrencyOptionChain: async function (symbol) { 214 | const url = process.env.NSE_CURRENCY_OPTIONS_API.replace( 215 | "PARAMETER", 216 | symbol 217 | ); 218 | return this.getData(url); 219 | }, 220 | bindEquityData(startegy, inputData, action) { 221 | startegy.trades.forEach((trade) => { 222 | let selector = "priceInfo"; 223 | let nseDataSelected = this.getObject(inputData, selector); 224 | if (nseDataSelected) { 225 | //trade.lasttradedprice = nseDataSelected.lastPrice; 226 | 227 | if (action == "updateltp") { 228 | trade.lasttradedprice = nseDataSelected.lastPrice; 229 | } else if (action == "updateexit") { 230 | 231 | trade.lasttradedprice = nseDataSelected.lastPrice; 232 | if (trade.isexit) { 233 | trade.price = nseDataSelected.lastPrice; 234 | } 235 | } else if (action == "updateall") { 236 | trade.price = trade.lasttradedprice = nseDataSelected.lastPrice; 237 | } 238 | } 239 | }); 240 | return startegy; 241 | }, 242 | bindOptionData(startegy, inputData, action) { 243 | startegy.trades.forEach((trade) => { 244 | 245 | 246 | logger.info("startegy.expiry", this.formatDate(startegy.expiry)); 247 | console.log("Foreach >>>>>>>>", this); 248 | let selector = 249 | "records.data[? expiryDate==`" + 250 | this.formatDate(startegy.expiry) + 251 | "` && strikePrice == `" + 252 | trade.selectedstrike + 253 | "`]." + 254 | (trade.tradetype == "Call" ? "CE" : "PE"); 255 | let nseDataSelected = this.getObject(inputData, selector); 256 | if (nseDataSelected && nseDataSelected[0]?.lastPrice) { 257 | if (action == "updateltp") { 258 | trade.lasttradedprice = nseDataSelected[0].lastPrice; 259 | } else if (action == "updateexit") { 260 | 261 | trade.lasttradedprice = nseDataSelected[0].lastPrice; 262 | if (trade.isexit) { 263 | trade.price = nseDataSelected[0].lastPrice; 264 | } 265 | } else if (action == "updateall") { 266 | trade.price = trade.lasttradedprice = nseDataSelected[0].lastPrice; 267 | } 268 | } 269 | }); 270 | return startegy; 271 | }, 272 | bindExpiriesData(startegy, inputData) { 273 | let selector = 'records.expiryDates[*].{"name": @ }'; 274 | let nseDataSelected = this.getObject(inputData, selector); 275 | startegy.expiries = nseDataSelected; 276 | return startegy; 277 | }, 278 | 279 | 280 | getCookies: async (instance, url_oc) => { 281 | if (nseCookies) { 282 | logger.info("Getting Cookie form cache"); 283 | return nseCookies; 284 | } 285 | try { 286 | logger.info("Getting Cookies"); 287 | const response = await instance.get(url_oc); 288 | nseCookies = response.headers['set-cookie'].join(); 289 | logger.info("Got Cookies"); 290 | return nseCookies; 291 | } catch (error) { 292 | if (error.response.status === 403) { 293 | logger.error("getCookies =========> error.status === 403"); 294 | await getCookies() 295 | } else { 296 | logger.error("getCookies =========> error"); 297 | } 298 | } 299 | }, 300 | 301 | getData: async function (url) { 302 | ///Ref: https://stackoverflow.com/questions/67864408/how-to-return-server-response-from-axios 303 | ///Ref: https://stackoverflow.com/questions/66905036/node-js-requests-get-returns-response-code-401-for-nse-india-website 304 | 305 | try { 306 | if (!url) { 307 | console.error("Url is empty or null.") 308 | return; 309 | } 310 | let headers = { 311 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', 312 | 'accept-language': 'en,gu;q=0.9,hi;q=0.8', 'accept-encoding': 'gzip, deflate, br' 313 | } 314 | 315 | const instance = axios.create({ 316 | baseURL: url, 317 | headers: headers, 318 | cookie: "" 319 | }); 320 | 321 | var cookies = await this.getCookies(instance, "https://www.nseindia.com/"); 322 | 323 | const instanceUrl = axios.create({ 324 | baseURL: "https://www.nseindia.com/", 325 | headers: headers, 326 | cookie: cookies 327 | 328 | }); 329 | 330 | logger.info("Calling URL:", url); 331 | var responce = await instanceUrl.get(url); 332 | return responce.data; 333 | 334 | } catch (e) { 335 | logger.error(e); 336 | return null; 337 | //return e.reponce.data 338 | } 339 | }, 340 | getTradeTypes: function (startegy) { 341 | var _result = this.getObject(startegy, this.Maps.getAllTradeType); 342 | logger.info("GetTradeTypes:>>", JSON.stringify(_result)); 343 | return _result; 344 | }, 345 | getObject: function (inputData, selector) { 346 | return jmespath.search(inputData, selector); 347 | }, 348 | csvJSON: function (csv) { 349 | ///copied from: https://stackoverflow.com/questions/27979002/convert-csv-data-into-json-format-using-javascript 350 | if (!csv) 351 | return; 352 | let lines = csv.split("\n"); 353 | let result = []; 354 | let headers = lines[0].split(",").map((x) => x.trim()); 355 | for (let i = 1; i < lines.length; i++) { 356 | let obj = {}; 357 | let currentline = lines[i].split(","); 358 | 359 | for (let j = 0; j < headers.length; j++) { 360 | if (currentline[j]?.trim().length > 0) { 361 | obj[headers[j]] = currentline[j].trim(); 362 | } 363 | } 364 | if (obj.SYMBOL && obj.SYMBOL != "Symbol") { 365 | result.push(obj); 366 | } 367 | } 368 | return JSON.stringify(result); //JSON 369 | }, 370 | setCacheObject: (key, value) => { 371 | logger.info("Setting cache value for:", key, "and value is:", value); 372 | }, 373 | GetLastThursdayOfMonth: (year, month) => { 374 | if (!year) { 375 | var _curretDate = new Date(); 376 | year = _curretDate.getFullYear(); 377 | month = _curretDate.getMonth() + 1; 378 | } 379 | var lastDay = new Date(year, month, 0); 380 | if (lastDay.getDay() < 4) { 381 | lastDay.setDate(lastDay.getDate() - 6); 382 | } 383 | lastDay.setDate(lastDay.getDate() - (lastDay.getDay() - 4)); 384 | return lastDay; 385 | }, 386 | formatDate: (dateString) => { 387 | logger.info("dateString:>>", dateString); 388 | if (!dateString) { 389 | dateString = this.GetLastThursdayOfMonth(null, null); 390 | } 391 | var dateArray = dateString.split("-"); 392 | var year = dateArray[0]; 393 | var month = dateArray[1]; 394 | var day = dateArray[2]; 395 | 396 | var months = [ 397 | "Jan", "Feb", "Mar", "Apr", 398 | "May", "Jun", "Jul", "Aug", 399 | "Sep", "Oct", "Nov", "Dec" 400 | ]; 401 | 402 | return day + "-" + months[Number(month) - 1] + "-" + year; 403 | }, 404 | getCacheObject: (key) => { 405 | 406 | }, 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | }; 415 | -------------------------------------------------------------------------------- /server/src/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cors = require("cors"); 3 | const app = express(); 4 | const mongoose = require("mongoose"); 5 | const helmet = require("helmet"); 6 | const strategyController = require("./controller/strategycontroller"); 7 | const portfolioCotroller = require("./controller/portfoliocotroller"); 8 | const tradeController = require("./controller/tradecontroller"); 9 | const dataProvider = require("./dataprovidercontroller/index"); 10 | const logger = require("./common/logs"); 11 | const myenv = process.env; 12 | const port = process.env.PORT || 9090; 13 | const enable_dataapi = process.env.ENABLE_DATAAPI || "true"; 14 | const conn_string = process.env.DBCONNECTIONSTRING; 15 | 16 | //const swaggerUi = require('swagger-ui-express'); 17 | //const swaggerDocument = require('./swagger.json'); 18 | //app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); 19 | 20 | 21 | 22 | //console.log("Connection string", conn_string); 23 | // logger.info("port:", port); 24 | app.use(express.json()); 25 | app.use(cors()); 26 | app.use(helmet()); 27 | app.use("/strategy", strategyController); 28 | app.use("/portfolio", portfolioCotroller); 29 | app.use("/trade", tradeController); 30 | 31 | app.get('/', function (req, res) { 32 | res.send("

helloworld

"); 33 | }); 34 | 35 | if (enable_dataapi == 'true') { 36 | app.use("/data", dataProvider); 37 | } else { 38 | app.use("/data", (req, res) => { 39 | res.status(404).json({ "error": "Data endpoint is disabled" }) 40 | }); 41 | } 42 | 43 | if (conn_string) { 44 | mongoose 45 | .connect(conn_string) 46 | .then(() => logger.info("MongoDB connected")) 47 | .catch((err) => logger.error(err)); 48 | } else { 49 | logger.error("Empty connnection string!") 50 | } 51 | 52 | app.listen(port, (x) => { 53 | logger.info("Service Started"); 54 | }); 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /server/src/models/commonUtility.js: -------------------------------------------------------------------------------- 1 | const { ObjectID } = require("mongodb"); 2 | const Portfolio = require("./portfolio"); 3 | const Strategy = require("./strategy"); 4 | const trade = require("./trade"); 5 | module.exports = { 6 | GetPortfolioById: async function (id) { 7 | var profolioObject = await Portfolio.findOne({ _id: id }); 8 | return profolioObject; 9 | }, 10 | 11 | GetStrategyById: async function (id) { 12 | var strategyObject = await Strategy.findOne({ 13 | _id: id, 14 | }); 15 | return strategyObject; 16 | }, 17 | GetTradeById: async function (id) { 18 | var tradeObject = await Strategy.findOne({ 19 | "trades._id": id, 20 | }); 21 | return tradeObject; 22 | }, 23 | 24 | DeleteStrategyUsingPortfolioID: function (pid) { 25 | if (pid) { 26 | if (process.env.ENABLE_DEMO == 'false') { 27 | Strategy.remove({ portfolio: pid }, (err, doc) => { 28 | return doc; 29 | }); 30 | } 31 | else 32 | { 33 | return null; 34 | } 35 | 36 | } 37 | }, 38 | 39 | }; 40 | -------------------------------------------------------------------------------- /server/src/models/portfolio.js: -------------------------------------------------------------------------------- 1 | //require("./strategy"); 2 | const mongoose = require('mongoose'); 3 | const schema = mongoose.Schema, 4 | model = mongoose.model.bind(mongoose); 5 | 6 | const portfolioSchema = schema({ 7 | name: { 8 | type : String 9 | }, 10 | exchange :{ 11 | type : String 12 | }, 13 | openingbalance :{ 14 | type : Number 15 | }, 16 | description:{ 17 | type : String 18 | }, 19 | order:{ 20 | type: Number 21 | }, 22 | createdon : { 23 | type: Date, 24 | default : Date.now() 25 | }, 26 | modifiedon : { 27 | type: Date, 28 | default : Date.now() 29 | } 30 | // Strategies : [strategySchema] 31 | }); 32 | 33 | module.exports = model("Portfolio", portfolioSchema); 34 | -------------------------------------------------------------------------------- /server/src/models/strategy.js: -------------------------------------------------------------------------------- 1 | require("./trade"); 2 | require("./portfolio"); 3 | const mongoose = require("mongoose"); 4 | const schema = mongoose.Schema, 5 | model = mongoose.model.bind(mongoose), 6 | tradeSchema = mongoose.model("Trade").schema, 7 | portfolioSchema = mongoose.model("Portfolio").schema; 8 | 9 | 10 | const strategySchema = schema({ 11 | name: { 12 | type: String, 13 | }, 14 | description: { 15 | type: String, 16 | }, 17 | symbol: { 18 | type: String, 19 | }, 20 | symboltype: { 21 | type: String, 22 | }, 23 | lotsize: { 24 | type: Number 25 | }, 26 | expiry: { 27 | type: String 28 | }, 29 | strikepricestep: { 30 | type: Number 31 | }, 32 | ismultiplesymbol: { 33 | type: Boolean, 34 | default: false 35 | }, 36 | hidechart: { 37 | type: Boolean, 38 | default: false 39 | }, 40 | isarchive: { 41 | type: Boolean, 42 | default: false 43 | }, 44 | createdon: { 45 | type: Date, 46 | default: Date.now(), 47 | }, 48 | modifiedon: { 49 | type: Date, 50 | default: Date.now(), 51 | }, 52 | trades: [tradeSchema], 53 | portfolio: { 54 | type: mongoose.Schema.Types.ObjectId, 55 | ref: "Portfolio" 56 | }, 57 | }); 58 | 59 | //name, description, symbol, createdon 60 | module.exports = model("Strategy", strategySchema); 61 | -------------------------------------------------------------------------------- /server/src/models/trade.js: -------------------------------------------------------------------------------- 1 | const { ObjectId } = require("bson"); 2 | const mongoose = require("mongoose"); 3 | const schema = mongoose.Schema, 4 | model = mongoose.model.bind(mongoose); 5 | const BUYORSELL = { 6 | // 'BUY': 0, 7 | // 'SELL': 1, 8 | }; 9 | const CALLPUTFUT = { 10 | // 'CALL': 0, 11 | // 'PUT': 1, 12 | // 'FUT': 2, 13 | }; 14 | 15 | const tradeSchema = schema({ 16 | symbol: { 17 | type: String 18 | }, 19 | lotsize: { 20 | type: Number 21 | }, 22 | // expiry: { 23 | // type: Date 24 | // }, 25 | buyorsell: { 26 | type: BUYORSELL, 27 | }, 28 | tradetype: { 29 | type: CALLPUTFUT, 30 | }, 31 | quantity: { 32 | type: Number, 33 | }, 34 | selectedstrike: { 35 | type: Number, 36 | }, 37 | price: { 38 | type: Number, 39 | }, 40 | lasttradedprice:{ 41 | type : Number, 42 | }, 43 | isexit: { 44 | type: Boolean, 45 | default: false, 46 | }, 47 | partnerid: { 48 | type: mongoose.Schema.Types.ObjectId, 49 | }, 50 | note: { 51 | type: String 52 | }, 53 | group: { 54 | type: String, 55 | }, 56 | order :{ 57 | type: Number 58 | }, 59 | color:{ 60 | type : String 61 | }, 62 | createdon: { 63 | type: Date, 64 | default: Date.now(), 65 | }, 66 | modifiedon: { 67 | type: Date, 68 | default: Date.now(), 69 | }, 70 | 71 | 72 | strikepricemin: { 73 | type: Number, 74 | default: 0 75 | }, 76 | strikepricemax: { 77 | type: Number, 78 | default: 0 79 | }, 80 | strikepricestep: { 81 | type: Number, 82 | default: 0 83 | }, 84 | 85 | 86 | }); 87 | 88 | module.exports = model("Trade", tradeSchema); 89 | -------------------------------------------------------------------------------- /server/src/swagger.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /server/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo docker stop $(sudo docker ps -a -q -f ancestor='ptserver') 4 | 5 | -------------------------------------------------------------------------------- /server/webpack.config.js: -------------------------------------------------------------------------------- 1 | //Copied from https://binyamin.medium.com/creating-a-node-express-webpack-app-with-dev-and-prod-builds-a4962ce51334 2 | 3 | const path = require('path') 4 | const nodeExternals = require('webpack-node-externals') 5 | const HtmlWebPackPlugin = require("html-webpack-plugin") 6 | module.exports = { 7 | // mode: "development", 8 | mode: "production", 9 | 10 | entry: { 11 | index: path.resolve(__dirname, './src/index.js'), 12 | }, 13 | output: { 14 | path: path.resolve(__dirname, "dist"), 15 | filename: '[name].js' 16 | }, 17 | target : 'node', 18 | externals: [nodeExternals()], 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | exclude: /node_modules/, 24 | use: { 25 | loader: "babel-loader" 26 | } 27 | } 28 | ] 29 | } 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | // entry: { 38 | // server: './src/index.js', 39 | // }, 40 | // output: { 41 | // path: path.join(__dirname, 'dist'), 42 | // publicPath: '/', 43 | // filename: 'index.js' 44 | // }, 45 | // target: 'node', 46 | // node: { 47 | // __dirname: false, 48 | // __filename: false, 49 | // }, 50 | // externals: [nodeExternals()], 51 | // module: { 52 | // rules: [ 53 | // { 54 | // test: /\.js$/, 55 | // exclude: /node_modules/, 56 | // use: { 57 | // loader: "babel-loader" 58 | // } 59 | // } 60 | // ] 61 | // } 62 | } 63 | --------------------------------------------------------------------------------