├── .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 | 
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 |
2 |
3 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
94 |
95 |
97 |
--------------------------------------------------------------------------------
/client/src/assets/grip.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
2 |
3 |
4 |
14 |
19 |
20 |
37 |
42 |
43 |
70 |
71 |
72 |
73 |
74 |
127 |
128 |
--------------------------------------------------------------------------------
/client/src/components/ui/DropDown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
66 |
67 |
68 |
69 |
119 |
124 |
--------------------------------------------------------------------------------
/client/src/components/ui/GroupButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/client/src/components/ui/SwitchButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
45 |
46 |
47 |
104 |
105 |
--------------------------------------------------------------------------------
/client/src/components/ui/ToolTip.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ Value }}
3 |
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 |
2 |
3 |
4 |
This is an about page
5 |
6 |
7 |
--------------------------------------------------------------------------------
/client/src/views/PaperTrade.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
18 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/client/src/views/PortfolioDetail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Please select a portfolio.
5 |
6 |
7 |
19 |
20 |
21 |
22 | {{ Portfolio.name }}
23 |
24 |
31 |
32 |
33 |
34 |
35 | {{ Portfolio.exchange }}
36 |
37 |
38 |
45 |
46 |
47 |
48 |
49 |
50 | {{ Portfolio.openingbalance | decimal2}}
51 |
52 |
53 |
60 |
61 |
62 |
63 |
64 |
65 | {{ CurrentBalance | decimal2 }}
66 |
67 |
68 |
69 |
70 |
71 | {{ TotalPortfolioAmount | decimal2}}
72 |
73 |
74 |
75 |
76 | {{ NAV | decimal2 }}%
77 |
78 |
79 |
80 |
87 |
91 |
95 |
102 |
103 |
104 |
105 |
106 |
115 |
116 |
117 |
118 |
200 |
201 |
218 |
--------------------------------------------------------------------------------
/client/src/views/PortfolioMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
27 |
28 |
29 |
41 |
54 |
55 |
56 |
62 |
83 |
84 | {{ item.name }}
85 |
86 |
102 |
103 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
241 |
258 |
--------------------------------------------------------------------------------
/client/src/views/StrategyDetail.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 | {{ PropStrategy.name }}
12 |
13 |
19 |
20 |
21 |
22 |
23 | {{ PropStrategy.symbol }}
24 |
25 |
26 |
33 |
34 |
35 |
36 |
37 | {{ PropStrategy.symboltype }}
38 |
39 |
46 |
47 |
48 |
49 |
50 | {{ PropStrategy.lotsize }}
51 |
52 |
53 |
59 |
60 |
61 |
62 |
63 | {{ PropStrategy.expiry | formatDateTime }}
64 |
65 |
66 |
73 |
74 |
75 |
78 |
79 | {{ PropStrategy.strikepricestep }}
80 |
81 |
82 |
88 |
89 |
90 |
91 | {{ PropStrategy.createdon | formatDateTime }}
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
109 |
110 |
114 |
115 |
126 |
127 |
137 |
138 |
139 |
148 |
149 |
150 |
157 |
158 |
159 |
160 |
161 |
162 |
174 |
175 |
180 |
181 | keyboard_double_arrow_right
182 |
183 |
184 | keyboard_double_arrow_left
185 |
186 |
187 |
188 |
189 |
193 |
194 |
195 |
218 |
219 |
220 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
384 |
385 |
402 |
403 |
--------------------------------------------------------------------------------
/client/src/views/TradeList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
12 |
13 |
Stike Price
14 |
Trade Type
15 |
B/S
16 |
Qty
17 |
Spot Price
18 |
19 | LTP
20 |
21 |
Total Price
22 |
23 |
24 |
31 |
32 |
36 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
57 |
70 |
71 |
74 |
75 |
79 |
80 |
81 |
82 |
83 |
87 |
88 | {{ item.selectedstrike }}
89 |
90 |
94 |
95 |
101 |
102 |
103 |
104 | {{ item.tradetype }}
105 |
106 |
107 |
112 |
113 |
114 |
115 |
116 | {{ item.buyorsell }}
117 |
118 |
119 |
120 |
121 |
122 | {{ item.quantity }}
123 |
124 |
126 |
127 |
128 |
129 | {{ item.price | decimal2 }}
130 |
131 |
133 |
134 |
135 |
136 |
140 |
141 |
142 | {{ item.lasttradedprice | decimal2 }}
143 |
144 |
145 |
146 |
147 | {{ item.buyorsell == "Buy" ? (item.price * (PropStrategy.lotsize * item.quantity) * -1).toFixed(2) :
148 | (item.price * (PropStrategy.lotsize * item.quantity)).toFixed(2) }}
149 |
150 |
151 |
152 |
156 |
160 |
164 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | P&L
190 |
191 |
192 | {{ TotalAmount | decimal2 }}
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 | This strategy is archived with P&L
202 |
203 | {{ TotalAmount | decimal2 }}
204 |
205 |
206 |
207 |
208 |
544 |
561 |
--------------------------------------------------------------------------------
/client/src/views/builder/Builder.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
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 |
--------------------------------------------------------------------------------