diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..ebb9ce78 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# top-most EditorConfig file +root = true + +# global baselines +[*] +charset = utf-8 +end_of_line = lf + +indent_style = space +indent_size = 2 +tab_width = 2 + +line_length = 150 +max_line_length = 150 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{xml,yml}] +indent_style = space +indent_size = 2 + +# see Client and Server folders for +# language unique editor configurations diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..bb763007 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "ms-azuretools.vscode-azurefunctions", + "ms-dotnettools.csharp" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..8d17a193 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to .NET Functions", + "type": "coreclr", + "request": "attach", + "processId": "${command:azureFunctions.pickProcess}" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..3fac52b0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "azureFunctions.projectSubpath": "Server\\Functions", + "azureFunctions.deploySubpath": "Server\\Functions/bin/Release/net8.0/publish", + "azureFunctions.projectLanguage": "C#", + "azureFunctions.projectRuntime": "~4", + "debug.internalConsoleOptions": "neverOpen", + "azureFunctions.preDeployTask": "publish (functions)" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..6415e241 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,81 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "clean (functions)", + "command": "dotnet", + "args": [ + "clean", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/Server\\Functions" + } + }, + { + "label": "build (functions)", + "command": "dotnet", + "args": [ + "build", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "dependsOn": "clean (functions)", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/Server\\Functions" + } + }, + { + "label": "clean release (functions)", + "command": "dotnet", + "args": [ + "clean", + "--configuration", + "Release", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/Server\\Functions" + } + }, + { + "label": "publish (functions)", + "command": "dotnet", + "args": [ + "publish", + "--configuration", + "Release", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "dependsOn": "clean release (functions)", + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/Server\\Functions" + } + }, + { + "type": "func", + "dependsOn": "build (functions)", + "options": { + "cwd": "${workspaceFolder}/Server\\Functions/bin/Debug/net8.0" + }, + "command": "host start", + "isBackground": true, + "problemMatcher": "$func-dotnet-watch" + } + ] +} diff --git a/Client/.editorconfig b/Client/.editorconfig deleted file mode 100644 index 1239f7e1..00000000 --- a/Client/.editorconfig +++ /dev/null @@ -1,9 +0,0 @@ -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -max_line_length = 150 diff --git a/Client/.vscode/settings.json b/Client/.vscode/settings.json index 75ceabc3..8dbcd2a1 100644 --- a/Client/.vscode/settings.json +++ b/Client/.vscode/settings.json @@ -1,35 +1,35 @@ { - "files.autoSave": "afterDelay", - "files.autoSaveDelay": 3000, - "search.exclude": { - "**/dist": true, - "**/node_modules": true, - "**/package-lock.json": true - }, - "editor.bracketPairColorization.enabled": true, - "editor.guides.bracketPairs": "active", - "editor.formatOnSave": true, - "cSpell.words": [ - "apidata", - "browserconfig", - "chartjs", - "colorpicker", - "devkit", - "ecoysystem", - "linetype", - "linewidth", - "lookback", - "MACD", - "matero", - "maxwidth", - "noselect", - "Ohlc", - "SUPERTREND", - "timeseries", - "tinycolor", - "ucid", - "uiid", - "unsavable", - "Xaxes" - ] -} \ No newline at end of file + "files.autoSave": "afterDelay", + "files.autoSaveDelay": 3000, + "search.exclude": { + "**/dist": true, + "**/node_modules": true, + "**/package-lock.json": true + }, + "editor.bracketPairColorization.enabled": true, + "editor.guides.bracketPairs": "active", + "editor.formatOnSave": true, + "cSpell.words": [ + "apidata", + "browserconfig", + "chartjs", + "colorpicker", + "devkit", + "ecoysystem", + "linetype", + "linewidth", + "lookback", + "MACD", + "matero", + "maxwidth", + "noselect", + "Ohlc", + "SUPERTREND", + "timeseries", + "tinycolor", + "ucid", + "uiid", + "unsavable", + "Xaxes" + ] +} diff --git a/Client/angular.json b/Client/angular.json index 8a6da5e1..260bc3a8 100644 --- a/Client/angular.json +++ b/Client/angular.json @@ -38,7 +38,11 @@ } ], "stylePreprocessorOptions": { - "includePaths": ["node_modules", "src", "src/styles"] + "includePaths": [ + "node_modules", + "src", + "src/styles" + ] }, "scripts": [], "allowedCommonJsDependencies": [ diff --git a/Client/package-lock.json b/Client/package-lock.json index c6ed036c..9dd533ac 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -7,18 +7,18 @@ "": { "name": "stock.charts", "dependencies": { - "@angular/animations": "17.3.6", - "@angular/cdk": "17.3.6", - "@angular/common": "17.3.6", - "@angular/compiler": "17.3.6", - "@angular/core": "17.3.6", - "@angular/forms": "17.3.6", - "@angular/material": "17.3.6", - "@angular/platform-browser": "17.3.6", - "@angular/platform-browser-dynamic": "17.3.6", - "@angular/router": "17.3.6", + "@angular/animations": "17.3.7", + "@angular/cdk": "17.3.7", + "@angular/common": "17.3.7", + "@angular/compiler": "17.3.7", + "@angular/core": "17.3.7", + "@angular/forms": "17.3.7", + "@angular/material": "17.3.7", + "@angular/platform-browser": "17.3.7", + "@angular/platform-browser-dynamic": "17.3.7", + "@angular/router": "17.3.7", "@ctrl/tinycolor": "4.1.0", - "@ng-matero/extensions": "17.2.2", + "@ng-matero/extensions": "17.3.0", "chart.js": "4.4.2", "chartjs-adapter-date-fns": "3.0.0", "chartjs-plugin-annotation": "3.0.1", @@ -27,12 +27,12 @@ "ngx-color": "9.0.0", "rxjs": "7.8.1", "tslib": "2.6.2", - "zone.js": "0.14.4" + "zone.js": "0.14.5" }, "devDependencies": { "@angular-devkit/build-angular": "17.3.6", "@angular/cli": "17.3.6", - "@angular/compiler-cli": "17.3.6", + "@angular/compiler-cli": "17.3.7", "typescript": "5.4.5" } }, @@ -258,9 +258,9 @@ } }, "node_modules/@angular/animations": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.6.tgz", - "integrity": "sha512-ev99cnmc1S/SXYz9OwOyZQyHXHiUf+ZwQFpjYBRPoyKqZV4sOYMlyBbfjBO/GgCVrsGfMvBsCI6PtY3yquuabA==", + "version": "17.3.7", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.7.tgz", + "integrity": "sha512-ahenGALPPweeHgqtl9BMkGIAV4fUNI5kOWUrLNbKBfwIJN+aOBOYV1Jz6NKUQq6eYn/1ZYtm0f3lIkHIdtLKEw==", "dependencies": { "tslib": "^2.3.0" }, @@ -268,13 +268,13 @@ "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/core": "17.3.6" + "@angular/core": "17.3.7" } }, "node_modules/@angular/cdk": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.6.tgz", - "integrity": "sha512-7eKrC61/6pmMAxllU/vYKadZRF7x7GxUYpA5G70fNaQsIUUiZvxx/SJN9AuZEoPGAtF6atKlJD8QVmFoDzv/Lw==", + "version": "17.3.7", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.7.tgz", + "integrity": "sha512-aFEh8tzKFOwini6aNEp57S54Ocp9T7YIJfBVMESptu2TCPdMTlJ1HJTg5XS8NcQO+vwi9cFPGVwGF1frOx4LXA==", "dependencies": { "tslib": "^2.3.0" }, @@ -322,9 +322,9 @@ } }, "node_modules/@angular/common": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.6.tgz", - "integrity": "sha512-ufviCFzQQKWcwc2j3Zi8bHbwkvqh4QU6GDH0u0usOee8xd8KrjgcYl3vD0r1/yxlDsd53Wg9kNRvz/fY+5qQoQ==", + "version": "17.3.7", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.7.tgz", + "integrity": "sha512-A7LRJu1vVCGGgrfZXjU+njz50SiU4weheKCar5PIUprcdIofS1IrHAJDqYh+kwXxkjXbZMOr/ijQY0+AESLEsw==", "dependencies": { "tslib": "^2.3.0" }, @@ -332,14 +332,14 @@ "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/core": "17.3.6", + "@angular/core": "17.3.7", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.6.tgz", - "integrity": "sha512-ybx9O76RGv4J97IThiSVvvWukuGcuXu50KsBDPUd874BFT3ml0OcRGhXoMh/isz7EQipiiGgsA51cJVTLES5Zw==", + "version": "17.3.7", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.7.tgz", + "integrity": "sha512-AlKiqPoxnrpQ0hn13fIaQPSVodaVAIjBW4vpFyuKFqs2LBKg6iolwZ21s8rEI0KR2gXl+8ugj0/UZ6YADiVM5w==", "dependencies": { "tslib": "^2.3.0" }, @@ -347,7 +347,7 @@ "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/core": "17.3.6" + "@angular/core": "17.3.7" }, "peerDependenciesMeta": { "@angular/core": { @@ -356,9 +356,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.6.tgz", - "integrity": "sha512-LaoUkY6uzcNocIEHJBvexvuU0a333IRQaG3Sj5IXhM1t864wTsfycn6yWJcQ7PhklB8BtNqiMbUQuEFtkxT8pg==", + "version": "17.3.7", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.7.tgz", + "integrity": "sha512-vSg5IQZ9jGmvYjpbfH8KbH4Sl1IVeE+Mr1ogcxkGEsURSRvKo7EWc0K7LSEI9+gg0VLamMiP9EyCJdPxiJeLJQ==", "dev": true, "dependencies": { "@babel/core": "7.23.9", @@ -379,7 +379,7 @@ "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/compiler": "17.3.6", + "@angular/compiler": "17.3.7", "typescript": ">=5.2 <5.5" } }, @@ -429,9 +429,9 @@ } }, "node_modules/@angular/core": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.6.tgz", - "integrity": "sha512-8IoeZVNqyeHA+H2dR3VFfz76/TFN1BpXP0aABs2aIUNVQRYlKxALSm1UlavijX8IT0uvd/6GXwE3WgymTcg0wg==", + "version": "17.3.7", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.7.tgz", + "integrity": "sha512-HWcrbxqnvIMSxFuQdN0KPt08bc87hqr0LKm89yuRTUwx/2sNJlNQUobk6aJj4trswGBttcRDT+GOS4DQP2Nr4g==", "dependencies": { "tslib": "^2.3.0" }, @@ -444,9 +444,9 @@ } }, "node_modules/@angular/forms": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.6.tgz", - "integrity": "sha512-WXxWhwvgRfYLNP2dB4Qe83tavEh2LnS4H0uoiecWHXijW2R9z8304X1vEyS1EtQK7o/s8fCVDVDjeY+hxLnCLw==", + "version": "17.3.7", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.7.tgz", + "integrity": "sha512-FEhXh/VmT++XCoO8i7bBtzxG7Am/cE1zrr9aF+fWW+4jpWvJvVN1IaSiJxgBB+iPsOJ9lTBRwfRW3onlcDkhrw==", "dependencies": { "tslib": "^2.3.0" }, @@ -454,16 +454,16 @@ "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/common": "17.3.6", - "@angular/core": "17.3.6", - "@angular/platform-browser": "17.3.6", + "@angular/common": "17.3.7", + "@angular/core": "17.3.7", + "@angular/platform-browser": "17.3.7", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.6.tgz", - "integrity": "sha512-sttN0JNvd2QvCCFIsxb5noiy7tgQdWrwvmrkJ+3KguHh5X84jDliA/d8N7Xgy2IBLnS/q/Hl9DdRCOiItWG1bw==", + "version": "17.3.7", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.7.tgz", + "integrity": "sha512-wjSKkk9KZE8QiBPkMd5axh5u/3pUSxoLKNO7OasFhEagMmSv5oYTLm40cErhtb4UdkSmbC19WuuluS6P3leoPA==", "dependencies": { "@material/animation": "15.0.0-canary.7f224ddd4.0", "@material/auto-init": "15.0.0-canary.7f224ddd4.0", @@ -516,7 +516,7 @@ }, "peerDependencies": { "@angular/animations": "^17.0.0 || ^18.0.0", - "@angular/cdk": "17.3.6", + "@angular/cdk": "17.3.7", "@angular/common": "^17.0.0 || ^18.0.0", "@angular/core": "^17.0.0 || ^18.0.0", "@angular/forms": "^17.0.0 || ^18.0.0", @@ -525,9 +525,9 @@ } }, "node_modules/@angular/platform-browser": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.6.tgz", - "integrity": "sha512-UikrgvMwtZIXp2pCP5AtkM7ibz2B5wBiGpnhhkYsqHKy9ndKVDA+3B5Z+/j9xeYYdsJAAtHl45zqILewyg+4iw==", + "version": "17.3.7", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.7.tgz", + "integrity": "sha512-Nn8ZMaftAvO9dEwribWdNv+QBHhYIBrRkv85G6et80AXfXoYAr/xcfnQECRFtZgPmANqHC5auv/xrmExQG+Yeg==", "dependencies": { "tslib": "^2.3.0" }, @@ -535,9 +535,9 @@ "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/animations": "17.3.6", - "@angular/common": "17.3.6", - "@angular/core": "17.3.6" + "@angular/animations": "17.3.7", + "@angular/common": "17.3.7", + "@angular/core": "17.3.7" }, "peerDependenciesMeta": { "@angular/animations": { @@ -546,9 +546,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.6.tgz", - "integrity": "sha512-dI+mgEROmSll042+XqkSsvkMQe6Et6L9BBiYYe7VbIFaRR9Dz5Pw2SeBLb+Ou+gWaxXc2Wc+13n442WEYWZ7Ew==", + "version": "17.3.7", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.7.tgz", + "integrity": "sha512-9c2I4u0L1p2v1/lW8qy+WaNHisUWbyy6wqsv2v9FfCaSM49Lxymgo9LPFPC4qEG5ei5nE+eIQ2ocRiXXsf5QkQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -556,16 +556,16 @@ "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/common": "17.3.6", - "@angular/compiler": "17.3.6", - "@angular/core": "17.3.6", - "@angular/platform-browser": "17.3.6" + "@angular/common": "17.3.7", + "@angular/compiler": "17.3.7", + "@angular/core": "17.3.7", + "@angular/platform-browser": "17.3.7" } }, "node_modules/@angular/router": { - "version": "17.3.6", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.6.tgz", - "integrity": "sha512-Gws3zukTlPO5lIGP0bmWBkmbRIRKvpPq6vs3BqQlbKsrfBh45SPvIRbx+BSv6WYUchQzfW7DFDXnQtiTEGGQNg==", + "version": "17.3.7", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.7.tgz", + "integrity": "sha512-lMkuRrc1ZjP5JPDxNHqoAhB0uAnfPQ/q6mJrw1s8IZoVV6VyM+FxR5r13ajNcXWC38xy/YhBjpXPF1vBdxuLXg==", "dependencies": { "tslib": "^2.3.0" }, @@ -573,9 +573,9 @@ "node": "^18.13.0 || >=20.9.0" }, "peerDependencies": { - "@angular/common": "17.3.6", - "@angular/core": "17.3.6", - "@angular/platform-browser": "17.3.6", + "@angular/common": "17.3.7", + "@angular/core": "17.3.7", + "@angular/platform-browser": "17.3.7", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -3668,9 +3668,9 @@ } }, "node_modules/@ng-matero/extensions": { - "version": "17.2.2", - "resolved": "https://registry.npmjs.org/@ng-matero/extensions/-/extensions-17.2.2.tgz", - "integrity": "sha512-mAdRYDCMdWFLX0EkjYBKE+TAGkHzljyZdItCWw7OnI/d/uRDIEhOutfS1FTh3U60Qs+W4AYIIMAZSYSJMnyFIw==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/@ng-matero/extensions/-/extensions-17.3.0.tgz", + "integrity": "sha512-m9OxUIfpyfGxcHCeq5zc/IG+OhKDzXMvxzhwRYco+qFA5gcYQ0LmkgGs2dbdzCSE1c9q65D+OupOHq2cM3DY+g==", "dependencies": { "@ng-select/ng-select": "^12.0.0", "ngx-color": "^9.0.0", @@ -12460,9 +12460,9 @@ } }, "node_modules/zone.js": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.4.tgz", - "integrity": "sha512-NtTUvIlNELez7Q1DzKVIFZBzNb646boQMgpATo9z3Ftuu/gWvzxCW7jdjcUDoRGxRikrhVHB/zLXh1hxeJawvw==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.5.tgz", + "integrity": "sha512-9XYWZzY6PhHOSdkYryNcMm7L8EK7a4q+GbTvxbIA2a9lMdRUpGuyaYvLDcg8D6bdn+JomSsbPcilVKg6SmUx6w==", "dependencies": { "tslib": "^2.3.0" } diff --git a/Client/package.json b/Client/package.json index 5a6118db..5d429324 100644 --- a/Client/package.json +++ b/Client/package.json @@ -9,18 +9,18 @@ }, "private": false, "dependencies": { - "@angular/animations": "17.3.6", - "@angular/cdk": "17.3.6", - "@angular/common": "17.3.6", - "@angular/compiler": "17.3.6", - "@angular/core": "17.3.6", - "@angular/forms": "17.3.6", - "@angular/material": "17.3.6", - "@angular/platform-browser": "17.3.6", - "@angular/platform-browser-dynamic": "17.3.6", - "@angular/router": "17.3.6", + "@angular/animations": "17.3.7", + "@angular/cdk": "17.3.7", + "@angular/common": "17.3.7", + "@angular/compiler": "17.3.7", + "@angular/core": "17.3.7", + "@angular/forms": "17.3.7", + "@angular/material": "17.3.7", + "@angular/platform-browser": "17.3.7", + "@angular/platform-browser-dynamic": "17.3.7", + "@angular/router": "17.3.7", "@ctrl/tinycolor": "4.1.0", - "@ng-matero/extensions": "17.2.2", + "@ng-matero/extensions": "17.3.0", "chart.js": "4.4.2", "chartjs-adapter-date-fns": "3.0.0", "chartjs-plugin-annotation": "3.0.1", @@ -29,12 +29,12 @@ "ngx-color": "9.0.0", "rxjs": "7.8.1", "tslib": "2.6.2", - "zone.js": "0.14.4" + "zone.js": "0.14.5" }, "devDependencies": { "@angular-devkit/build-angular": "17.3.6", "@angular/cli": "17.3.6", - "@angular/compiler-cli": "17.3.6", + "@angular/compiler-cli": "17.3.7", "typescript": "5.4.5" } -} \ No newline at end of file +} diff --git a/Client/src/app/chart/chart.component.scss b/Client/src/app/chart/chart.component.scss index 42ae2e86..a9c376f6 100644 --- a/Client/src/app/chart/chart.component.scss +++ b/Client/src/app/chart/chart.component.scss @@ -43,9 +43,10 @@ } // tablet-lg, landscape - @media (max-width: 880px) and (orientation: landscape){ + @media (max-width: 880px) and (orientation: landscape) { aspect-ratio: unset; - height: 25vh; } + height: 25vh; + } // mobile, landscape @media (max-height: 600px) and (orientation: landscape) { diff --git a/Client/src/app/picker/pick-config.component.html b/Client/src/app/picker/pick-config.component.html index 6b2abb40..2a43de41 100644 --- a/Client/src/app/picker/pick-config.component.html +++ b/Client/src/app/picker/pick-config.component.html @@ -38,7 +38,8 @@

{{r.displayName}}

Line width - + {{width.name}} diff --git a/Client/src/app/picker/settings.component.ts b/Client/src/app/picker/settings.component.ts index d0d7be20..9fd991c7 100644 --- a/Client/src/app/picker/settings.component.ts +++ b/Client/src/app/picker/settings.component.ts @@ -51,11 +51,18 @@ export class SettingsComponent { // close current settings dialog this.listRef.closeAll(); - // open settings for indicator to add + // open indicator settings for indicator to add this.picker.open(PickConfigComponent, { autoFocus: "dialog", data: listing - }); + }).afterClosed() + + // reopen main settings after close + .subscribe(() => { + this.listRef.open(SettingsComponent, { + autoFocus: "dialog" + }); + }); } closeListDialog() { diff --git a/Client/src/assets/manifest.json b/Client/src/assets/manifest.json index 263a1871..d4ce7f01 100644 --- a/Client/src/assets/manifest.json +++ b/Client/src/assets/manifest.json @@ -1,6 +1,6 @@ { - "name": "stock chart | Stock Indicators for .NET", - "short_name": "Stock Chart", + "name": "Stock chart (demo) | Stock Indicators for .NET", + "short_name": "Stock chart", "description": "A stock chart to demonstrate the Stock Indicators for .NET NuGet package.", "id": "/", "theme_color": "#212121", diff --git a/Client/src/environments/environment.interface.ts b/Client/src/environments/environment.interface.ts index f425b10c..185e47d9 100644 --- a/Client/src/environments/environment.interface.ts +++ b/Client/src/environments/environment.interface.ts @@ -1,4 +1,4 @@ export interface EnvConfig { - production: boolean; - api: string; + production: boolean; + api: string; } diff --git a/Client/src/index.html b/Client/src/index.html index 256de229..891be085 100644 --- a/Client/src/index.html +++ b/Client/src/index.html @@ -7,7 +7,8 @@ - + @@ -26,7 +27,8 @@ - + diff --git a/Client/src/styles/_base-elements.scss b/Client/src/styles/_base-elements.scss index e39ebb9a..290ad4b7 100644 --- a/Client/src/styles/_base-elements.scss +++ b/Client/src/styles/_base-elements.scss @@ -12,7 +12,8 @@ body { // TYPOGRAPHY -h1, h2 { +h1, +h2 { font-weight: 400; } diff --git a/Server/.editorconfig b/Server/.editorconfig index dc6141d7..1e675f2f 100644 --- a/Server/.editorconfig +++ b/Server/.editorconfig @@ -1,28 +1,11 @@ -# top-most EditorConfig file -root = true - -# global baselines -[*] -charset = utf-8 -end_of_line = lf - -indent_style = space -indent_size = 2 -tab_width = 2 - -line_length = 150 -max_line_length = 150 -trim_trailing_whitespace = true -insert_final_newline = true +# Server (.NET) editor configurations +# overload the root +root = false [*.{csproj,props,targets}] indent_style = space indent_size = 2 -[*.yml] -indent_style = space -indent_size = 2 - [*.{cs,vb}] tab_width = 4 indent_size = 4 @@ -121,11 +104,14 @@ csharp_style_inlined_variable_declaration = true:suggestion # braces csharp_prefer_braces = true:suggestion -csharp_prefer + csharp_new_line_before_open_brace = methods, properties, control_blocks, types csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = false +csharp_new_line_before_members_in_anonymous_types = false +csharp_new_line_between_query_expression_clauses = false # braces overrides csharp_type_declaration_braces = next_line diff --git a/Server/ChartBackend.sln b/Server/ChartBackend.sln index 963a6bee..5c7a71e8 100644 --- a/Server/ChartBackend.sln +++ b/Server/ChartBackend.sln @@ -1,5 +1,4 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.32014.148 MinimumVisualStudioVersion = 10.0.40219.1 diff --git a/Server/Functions/README.md b/Server/Functions/README.md index 8cf9b75b..a3480b3e 100644 --- a/Server/Functions/README.md +++ b/Server/Functions/README.md @@ -12,13 +12,23 @@ setx ALPACA_SECRET "YOUR ALPACA SECRET KEY" ## CRON - +For our demo, we'll generally cache QQQ and SPY daily quotes every minute +during extended market hours 8am-6pm M-F U.S. eastern time with CRON `"0 */1 08-18 * * 1-5"`. + +- [CRON syntax documentation](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer#cron-expressions) + +To enable use of eastern time zone CRON values, we have configured our Azure Functions +to override the default UTC timezone; which is okay for our purposes. ## TIME ZONE -Azure App Service server app settings on the server: -`TZ` : `America/New_York` for Linux instance. +Azure App Service server app settings on Linux servers: + +>`TZ` : `America/New_York` for Linux instance. + +This is often documented for Windows instances servers as: -This is often documented as `WEBSITE_TIME_ZONE` : `Eastern Standard Time` for Windows instances. +>`WEBSITE_TIME_ZONE` : `Eastern Standard Time` -This is set in the Azure App Service settings online and potentially in the Release settings, not in any local settings files. +This is set in the Azure App Service settings online +and not in local app settings files. diff --git a/Server/Functions/UpdateQuotes.cs b/Server/Functions/UpdateQuotes.cs index 34fb8176..021461a2 100644 --- a/Server/Functions/UpdateQuotes.cs +++ b/Server/Functions/UpdateQuotes.cs @@ -11,6 +11,11 @@ public class Jobs(ILoggerFactory loggerFactory) { private readonly ILogger _logger = loggerFactory.CreateLogger(); + /// + /// Schedule to get and cache quotes from source feed. + /// + /// CRON-based schedule + /// [Function("UpdateQuotes")] public async Task Run([TimerTrigger("0 */1 08-18 * * 1-5")] TimerInfo myTimer) { @@ -24,7 +29,13 @@ public async Task Run([TimerTrigger("0 */1 08-18 * * 1-5")] TimerInfo myTimer) DateTime.Now, myTimer.ScheduleStatus); } - // STORE QUOTE + /// + /// STORE QUOTES: get and store historical quotes to blob storage provider. + /// + /// Security symbol + /// + /// When credentials are missing + /// private async Task StoreQuoteDaily(string symbol) { // get and validate keys, see README.md @@ -42,7 +53,7 @@ private async Task StoreQuoteDaily(string symbol) { throw new ArgumentNullException( ALPACA_SECRET, - $"API SECRET missing, use `setx AlpacaApiSecret \"MY-ALPACA-SECRET\"` to set."); + $"API SECRET missing, use `setx ALPACA_SECRET \"MY-ALPACA-SECRET\"` to set."); } // fetch from Alpaca paper trading API diff --git a/Server/Functions/host.json b/Server/Functions/host.json index ee5cf5f8..2e24a7f5 100644 --- a/Server/Functions/host.json +++ b/Server/Functions/host.json @@ -1,12 +1,16 @@ { - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - }, - "enableLiveMetricsFilters": true - } + "version": "2.0", + "logging": { + "fileLoggingMode": "debugOnly", + "logLevel": { + "Function.UpdateQuotes": "Information", + "default": "None" + }, + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } } -} \ No newline at end of file + } +} diff --git a/Server/Functions/local.settings.json b/Server/Functions/local.settings.json index 64a872fc..8eea88f4 100644 --- a/Server/Functions/local.settings.json +++ b/Server/Functions/local.settings.json @@ -1,7 +1,7 @@ { - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" - } -} \ No newline at end of file + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" + } +} diff --git a/Server/WebApi/Controllers/MainController.cs b/Server/WebApi/Controllers/MainController.cs index 89d727c5..213bfbd7 100644 --- a/Server/WebApi/Controllers/MainController.cs +++ b/Server/WebApi/Controllers/MainController.cs @@ -1,13 +1,14 @@ using Microsoft.AspNetCore.Mvc; -using Skender.Stock.Indicators; using WebApi.Services; namespace WebApi.Controllers; [ApiController] [Route("")] -public class MainController : ControllerBase +public class MainController(QuoteService quoteService) : ControllerBase { + private readonly QuoteService quoteFeed = quoteService; + // GLOBALS internal static readonly int limitLast = 120; @@ -15,25 +16,27 @@ public class MainController : ControllerBase public string Get() => "API is functioning nominally."; [HttpGet("quotes")] - public IActionResult GetQuotes() + public async Task GetQuotes() { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); return Ok(quotes.TakeLast(limitLast)); } [HttpGet("indicators")] - public IActionResult GetIndicators() => Ok(Metadata.IndicatorList($"{Request.Scheme}://{Request.Host}")); + public IActionResult GetIndicators() + => Ok(Metadata.IndicatorList($"{Request.Scheme}://{Request.Host}")); + ////////////////////////////////////////// // INDICATORS (sorted alphabetically) [HttpGet("ADL")] - public IActionResult GetAdl( + public async Task GetAdl( int smaPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetAdl(smaPeriods) @@ -48,12 +51,12 @@ public IActionResult GetQuotes() } [HttpGet("ADX")] - public IActionResult GetAdx( + public async Task GetAdx( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetAdx(lookbackPeriods) @@ -68,14 +71,14 @@ public IActionResult GetQuotes() } [HttpGet("ALMA")] - public IActionResult GetAlma( + public async Task GetAlma( int lookbackPeriods, double offset, double sigma) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetAlma(lookbackPeriods, offset, sigma) @@ -90,7 +93,7 @@ public IActionResult GetQuotes() } [HttpGet("ALLIGATOR")] - public IActionResult GetAlligator( + public async Task GetAlligator( int jawPeriods, int jawOffset, int teethPeriods, @@ -100,7 +103,7 @@ public IActionResult GetQuotes() { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetAlligator(jawPeriods, jawOffset, teethPeriods, teethOffset, lipsPeriods, lipsOffset) @@ -115,12 +118,12 @@ public IActionResult GetQuotes() } [HttpGet("AROON")] - public IActionResult GetAroon( + public async Task GetAroon( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetAroon(lookbackPeriods) @@ -135,12 +138,12 @@ public IActionResult GetQuotes() } [HttpGet("ATR")] - public IActionResult GetAtr( - int lookbackPeriods) + public async Task GetAtr( + int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetAtr(lookbackPeriods) @@ -155,13 +158,13 @@ public IActionResult GetQuotes() } [HttpGet("ATR-STOP-CLOSE")] - public IActionResult GetAtrStopClose( + public async Task GetAtrStopClose( int lookbackPeriods, double multiplier) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetAtrStop(lookbackPeriods, multiplier, EndType.Close) @@ -176,13 +179,13 @@ public IActionResult GetQuotes() } [HttpGet("ATR-STOP-HL")] - public IActionResult GetAtrStopHL( + public async Task GetAtrStopHL( int lookbackPeriods, double multiplier) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetAtrStop(lookbackPeriods, multiplier, EndType.HighLow) @@ -197,13 +200,13 @@ public IActionResult GetQuotes() } [HttpGet("BB")] - public IActionResult GetBollingerBands( + public async Task GetBollingerBands( int lookbackPeriods, double standardDeviations) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetBollingerBands(lookbackPeriods, standardDeviations) @@ -218,14 +221,14 @@ public IActionResult GetQuotes() } [HttpGet("BETA")] - public IActionResult GetBeta( + public async Task GetBeta( int lookbackPeriods, BetaType type) { try { - IEnumerable quotes = FetchQuotes.Get(); - IEnumerable market = FetchQuotes.Get("SPY", "DAILY"); + IEnumerable quotes = await quoteFeed.Get(); + IEnumerable market = await quoteFeed.Get("SPY"); IEnumerable results = quotes.GetBeta(market, lookbackPeriods, type) @@ -240,13 +243,13 @@ public IActionResult GetQuotes() } [HttpGet("CHEXIT-LONG")] - public IActionResult GetChandelierLong( + public async Task GetChandelierLong( int lookbackPeriods, double multiplier) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetChandelier(lookbackPeriods, multiplier, ChandelierType.Long) @@ -261,13 +264,13 @@ public IActionResult GetQuotes() } [HttpGet("CHEXIT-SHORT")] - public IActionResult GetChandelierShort( + public async Task GetChandelierShort( int lookbackPeriods, double multiplier) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetChandelier(lookbackPeriods, multiplier, ChandelierType.Short) @@ -282,12 +285,12 @@ public IActionResult GetQuotes() } [HttpGet("CHOP")] - public IActionResult GetChop( + public async Task GetChop( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetChop(lookbackPeriods) @@ -302,12 +305,12 @@ public IActionResult GetQuotes() } [HttpGet("CMF")] - public IActionResult GetCmf( + public async Task GetCmf( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetCmf(lookbackPeriods) @@ -322,12 +325,12 @@ public IActionResult GetQuotes() } [HttpGet("CMO")] - public IActionResult GetCmo( + public async Task GetCmo( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetCmo(lookbackPeriods) @@ -342,14 +345,14 @@ public IActionResult GetQuotes() } [HttpGet("CRSI")] - public IActionResult GetConnorsRsi( + public async Task GetConnorsRsi( int rsiPeriods, int streakPeriods, int rankPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetConnorsRsi(rsiPeriods, streakPeriods, rankPeriods) @@ -364,12 +367,12 @@ public IActionResult GetQuotes() } [HttpGet("DOJI")] - public IActionResult GetDoji( + public async Task GetDoji( double maxPriceChangePercent) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetDoji(maxPriceChangePercent) @@ -384,12 +387,12 @@ public IActionResult GetQuotes() } [HttpGet("DONCHIAN")] - public IActionResult GetDonchian( + public async Task GetDonchian( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetDonchian(lookbackPeriods) @@ -404,12 +407,12 @@ public IActionResult GetQuotes() } [HttpGet("DYN")] - public IActionResult GetDynamic( - int lookbackPeriods) + public async Task GetDynamic( + int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetDynamic(lookbackPeriods) @@ -424,12 +427,12 @@ public IActionResult GetQuotes() } [HttpGet("ELDER-RAY")] - public IActionResult GetElderRay( + public async Task GetElderRay( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetElderRay(lookbackPeriods) @@ -444,12 +447,12 @@ public IActionResult GetQuotes() } [HttpGet("EPMA")] - public IActionResult GetEpma( + public async Task GetEpma( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetEpma(lookbackPeriods) @@ -464,12 +467,12 @@ public IActionResult GetQuotes() } [HttpGet("EMA")] - public IActionResult GetEma( + public async Task GetEma( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetEma(lookbackPeriods) @@ -484,12 +487,12 @@ public IActionResult GetQuotes() } [HttpGet("FCB")] - public IActionResult GetFcb( + public async Task GetFcb( int windowSpan) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetFcb(windowSpan) @@ -504,12 +507,12 @@ public IActionResult GetQuotes() } [HttpGet("FISHER")] - public IActionResult GetFisher( + public async Task GetFisher( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetFisherTransform(lookbackPeriods) @@ -524,12 +527,12 @@ public IActionResult GetQuotes() } [HttpGet("FRACTAL")] - public IActionResult GetFractal( + public async Task GetFractal( int windowSpan) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetFractal(windowSpan) @@ -544,11 +547,11 @@ public IActionResult GetQuotes() } [HttpGet("GATOR")] - public IActionResult GetGator() + public async Task GetGator() { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetGator() @@ -563,11 +566,11 @@ public IActionResult GetGator() } [HttpGet("HTL")] - public IActionResult GetHTL() + public async Task GetHTL() { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetHtTrendline() @@ -581,15 +584,37 @@ public IActionResult GetHTL() } } + [HttpGet("ICHIMOKU")] + public async Task GetIchimoku( + int tenkanPeriods, + int kijunPeriods, + int senkouBPeriods) + { + try + { + IEnumerable quotes = await quoteFeed.Get(); + + IEnumerable results = + quotes.GetIchimoku(tenkanPeriods, kijunPeriods, senkouBPeriods) + .TakeLast(limitLast); + + return Ok(results); + } + catch (ArgumentOutOfRangeException rex) + { + return BadRequest(rex.Message); + } + } + [HttpGet("KELTNER")] - public IActionResult GetKeltner( + public async Task GetKeltner( int emaPeriods, double multiplier, int atrPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetKeltner(emaPeriods, multiplier, atrPeriods) @@ -604,14 +629,14 @@ public IActionResult GetHTL() } [HttpGet("MACD")] - public IActionResult GetMacd( + public async Task GetMacd( int fastPeriods, int slowPeriods, int signalPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetMacd(fastPeriods, slowPeriods, signalPeriods) @@ -626,12 +651,12 @@ public IActionResult GetHTL() } [HttpGet("MARUBOZU")] - public IActionResult GetMarubozu( + public async Task GetMarubozu( double minBodyPercent) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetMarubozu(minBodyPercent) @@ -646,12 +671,12 @@ public IActionResult GetHTL() } [HttpGet("MFI")] - public IActionResult GetMfi( + public async Task GetMfi( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetMfi(lookbackPeriods) @@ -666,13 +691,13 @@ public IActionResult GetHTL() } [HttpGet("PSAR")] - public IActionResult GetParabolicSar( + public async Task GetParabolicSar( double accelerationStep, double maxAccelerationFactor) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetParabolicSar(accelerationStep, maxAccelerationFactor) @@ -687,13 +712,13 @@ public IActionResult GetHTL() } [HttpGet("ROC")] - public IActionResult GetRoc( + public async Task GetRoc( int lookbackPeriods, int smaPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetRoc(lookbackPeriods, smaPeriods) @@ -708,12 +733,12 @@ public IActionResult GetHTL() } [HttpGet("RSI")] - public IActionResult GetRsi( + public async Task GetRsi( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetRsi(lookbackPeriods) @@ -728,12 +753,12 @@ public IActionResult GetHTL() } [HttpGet("SLOPE")] - public IActionResult GetSlope( + public async Task GetSlope( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetSlope(lookbackPeriods) @@ -748,12 +773,12 @@ public IActionResult GetHTL() } [HttpGet("SMA")] - public IActionResult GetSma( + public async Task GetSma( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetSma(lookbackPeriods) @@ -768,7 +793,7 @@ public IActionResult GetHTL() } [HttpGet("SMI")] - public IActionResult GetSmi( + public async Task GetSmi( int lookbackPeriods, int firstSmoothPeriods, int secondSmoothPeriods, @@ -776,7 +801,7 @@ public IActionResult GetHTL() { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetSmi(lookbackPeriods, firstSmoothPeriods, secondSmoothPeriods, signalPeriods) @@ -791,14 +816,14 @@ public IActionResult GetHTL() } [HttpGet("STC")] - public IActionResult GetStc( + public async Task GetStc( int cyclePeriods, int fastPeriods, int slowPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetStc(cyclePeriods, fastPeriods, slowPeriods) @@ -813,14 +838,14 @@ public IActionResult GetHTL() } [HttpGet("STARC")] - public IActionResult GetStarc( + public async Task GetStarc( int smaPeriods, double multiplier, int atrPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetStarcBands(smaPeriods, multiplier, atrPeriods) @@ -835,13 +860,13 @@ public IActionResult GetHTL() } [HttpGet("STDEV")] - public IActionResult GetStdDev( + public async Task GetStdDev( int lookbackPeriods, int smaPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); // we don't ask for smaPeriods with Z-Score, handle smaPeriods = smaPeriods == 0 ? 1 : smaPeriods; @@ -859,13 +884,13 @@ public IActionResult GetHTL() } [HttpGet("STO")] - public IActionResult GetStoch( + public async Task GetStoch( int lookbackPeriods, int signalPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetStoch(lookbackPeriods, signalPeriods) @@ -880,7 +905,7 @@ public IActionResult GetHTL() } [HttpGet("STORSI")] - public IActionResult GetStochRsi( + public async Task GetStochRsi( int rsiPeriods, int stochPeriods, int signalPeriods, @@ -888,7 +913,7 @@ public IActionResult GetHTL() { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetStochRsi(rsiPeriods, stochPeriods, signalPeriods, smoothPeriods) @@ -903,13 +928,13 @@ public IActionResult GetHTL() } [HttpGet("SUPERTREND")] - public IActionResult GetSuperTrend( + public async Task GetSuperTrend( int lookbackPeriods, double multiplier) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetSuperTrend(lookbackPeriods, multiplier) @@ -924,12 +949,12 @@ public IActionResult GetHTL() } [HttpGet("ULCER")] - public IActionResult GetUlcer( + public async Task GetUlcer( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetUlcerIndex(lookbackPeriods) @@ -944,12 +969,12 @@ public IActionResult GetHTL() } [HttpGet("VORTEX")] - public IActionResult GetVortex( + public async Task GetVortex( int lookbackPeriods) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetVortex(lookbackPeriods) @@ -964,12 +989,12 @@ public IActionResult GetHTL() } [HttpGet("ZIGZAG-CLOSE")] - public IActionResult GetZigZagClose( + public async Task GetZigZagClose( decimal percentChange) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetZigZag(EndType.Close, percentChange) @@ -984,12 +1009,12 @@ public IActionResult GetHTL() } [HttpGet("ZIGZAG-HIGHLOW")] - public IActionResult GetZigZagHighLow( + public async Task GetZigZagHighLow( decimal percentChange) { try { - IEnumerable quotes = FetchQuotes.Get(); + IEnumerable quotes = await quoteFeed.Get(); IEnumerable results = quotes.GetZigZag(EndType.HighLow, percentChange) diff --git a/Server/WebApi/GlobalUsings.cs b/Server/WebApi/GlobalUsings.cs new file mode 100644 index 00000000..50f0e87f --- /dev/null +++ b/Server/WebApi/GlobalUsings.cs @@ -0,0 +1,5 @@ +global using System.Text; +global using Azure; +global using Azure.Storage.Blobs; +global using Azure.Storage.Blobs.Models; +global using Skender.Stock.Indicators; diff --git a/Server/WebApi/Program.cs b/Server/WebApi/Program.cs index c7d4a755..3127f6e4 100644 --- a/Server/WebApi/Program.cs +++ b/Server/WebApi/Program.cs @@ -1,5 +1,6 @@ // STARTUP CONFIGURATION +using Microsoft.Net.Http.Headers; using WebApi.Services; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); @@ -11,12 +12,13 @@ services.AddControllers(); // get CORS origins from appsettings -List origins = []; - // reminder: production origins defined in cloud host settings » API » CORS +// so these are really only for localhost / development IConfigurationSection corsOrigins = configuration.GetSection("CorsOrigins"); string? allowedOrigin = corsOrigins["Website"]; +List origins = []; + if (!string.IsNullOrEmpty(allowedOrigin)) { origins.Add(item: allowedOrigin); @@ -34,15 +36,16 @@ }); }); -Console.WriteLine($"CORS Origin: {corsOrigins["Website"]}"); +Console.WriteLine($"CORS Origin: {allowedOrigin}"); // register services -builder.Services.AddHostedService(); +services.AddHostedService(); +services.AddTransient(); // build application WebApplication app = builder.Build(); -// Configure the HTTP request pipeline. +// configure the HTTP request pipeline _ = app.Environment.IsDevelopment() ? app.UseDeveloperExceptionPage() : app.UseHsts(); @@ -51,5 +54,7 @@ app.UseRouting(); app.UseCors("CorsPolicy"); app.UseResponseCaching(); + +// controller endpoints app.MapControllers(); app.Run(); diff --git a/Server/WebApi/Services/Service.Metadata.cs b/Server/WebApi/Services/Service.Metadata.cs index f10367c0..966b5ce4 100644 --- a/Server/WebApi/Services/Service.Metadata.cs +++ b/Server/WebApi/Services/Service.Metadata.cs @@ -13,7 +13,9 @@ public static IEnumerable IndicatorList(string baseUrl) string darkGray = "#757575"; string darkGrayTransparent = "#75757515"; string thresholdRed = "#B71C1C70"; + string thresholdRedTransparent = "#B71C1C20"; string thresholdGreen = "#1B5E2070"; + string thresholdGreenTransparent = "#1B5E2020"; List listing = [ @@ -1300,6 +1302,97 @@ public static IEnumerable IndicatorList(string baseUrl) ] }, + // Ichimoku Cloud + new IndicatorList { + Name = "Ichimoku Cloud", + Uiid = "ICHIMOKU", + LegendTemplate = "ICHIMOKU([P1],[P2],[P3])", + Endpoint = $"{baseUrl}/ICHIMOKU/", + Category = "price-trend", + ChartType = "overlay", + Order = Order.BehindPrice, + Parameters = + [ + new() { + DisplayName = "Tenkan Periods", + ParamName = "tenkanPeriods", + DataType = "int", + DefaultValue = 9, + Minimum = 1, + Maximum = 250 + }, + new() { + DisplayName = "Kijun Periods", + ParamName = "kijunPeriods", + DataType = "int", + DefaultValue = 26, + Minimum = 2, + Maximum = 250 + }, + new() { + DisplayName = "Senkou Periods", + ParamName = "senkouBPeriods", + DataType = "int", + DefaultValue = 52, + Minimum = 3, + Maximum = 250 + } + ], + Results = [ + new() { + DisplayName = "Tenkan-sen", + TooltipTemplate = "ICHIMOKU([P1],[P2],[P3] Tenkan-sen", + DataName = "tenkanSen", + DataType = "number", + LineType = "solid", + LineWidth = 2, + DefaultColor = standardBlue, + }, + new() { + DisplayName = "Kijun-sen", + TooltipTemplate = "ICHIMOKU([P1],[P2],[P3] Kijun-sen", + DataName = "kijunSen", + DataType = "number", + LineType = "solid", + LineWidth = 2, + DefaultColor = standardOrange, + }, + new() { + DisplayName = "Senkou span A", + TooltipTemplate = "ICHIMOKU([P1],[P2],[P3] Senkou span A", + DataName = "senkouSpanA", + DataType = "number", + LineType = "solid", + LineWidth = 1.5f, + DefaultColor = thresholdGreen, + }, + new() { + DisplayName = "Senkou span B", + TooltipTemplate = "ICHIMOKU([P1],[P2],[P3] Senkou span B", + DataName = "senkouSpanB", + DataType = "number", + LineType = "solid", + LineWidth = 1.5f, + DefaultColor = thresholdRed, + Fill = new ChartFill { + Target = "-1", + ColorAbove = thresholdRedTransparent, + ColorBelow = thresholdGreenTransparent + } + }, + new() { + DisplayName = "Chikou span", + TooltipTemplate = "ICHIMOKU([P1],[P2],[P3] Chikou span", + DataName = "chikouSpan", + DataType = "number", + LineType = "solid", + LineWidth = 2, + DefaultColor = standardPurple, + } + ] + }, + + // Keltner Channels new IndicatorList { Name = "Keltner Channels", diff --git a/Server/WebApi/Services/Service.Quotes.cs b/Server/WebApi/Services/Service.Quotes.cs index f43b4a65..b4bfb1ad 100644 --- a/Server/WebApi/Services/Service.Quotes.cs +++ b/Server/WebApi/Services/Service.Quotes.cs @@ -1,28 +1,29 @@ using System.Text.Json; -using Azure; -using Azure.Storage.Blobs; -using Azure.Storage.Blobs.Models; -using Skender.Stock.Indicators; namespace WebApi.Services; -internal static class FetchQuotes +public class QuoteService { - internal static IEnumerable Get() - { - return Get("QQQ", "DAILY"); - } + private readonly IEnumerable backupQuotes = GetBackup(); - internal static IEnumerable Get( - string symbol, // "QQQ" - string timeSpan) // "DAILY" + /// + /// Get default quotes + /// + /// List of default quotes + public async Task> Get() => await Get("QQQ"); + + /// + /// Get quotes for a specific symbol. + /// + /// "SPY" or "QQQ" only, for now + public async Task> Get(string symbol) { - string blobName = $"{symbol}-{timeSpan}.json"; + string blobName = $"{symbol}-DAILY.json"; try { BlobClient blob = Storage.GetBlobClient(blobName); - Response response = blob.Download(); + Response response = await blob.DownloadAsync(); Stream stream = response.Value.Content; @@ -39,8 +40,6 @@ internal static IEnumerable Get() } } - private static readonly IEnumerable backupQuotes = GetBackup(); - private static IEnumerable GetBackup() { List h = diff --git a/Server/WebApi/Services/Service.Storage.cs b/Server/WebApi/Services/Service.Storage.cs index d6ad3dfb..2cb4bfc7 100644 --- a/Server/WebApi/Services/Service.Storage.cs +++ b/Server/WebApi/Services/Service.Storage.cs @@ -1,16 +1,14 @@ -using System.Text; -using Azure; -using Azure.Storage.Blobs; -using Azure.Storage.Blobs.Models; - namespace WebApi.Services; public static class Storage { - // STARTUP - public static async Task Startup(CancellationToken cancellationToken) + /// + /// Initialize Azure services (setup blob storage for quotes) + /// + /// + /// + public static async Task Initialize(CancellationToken cancellationToken) { - // initialize Azure services (setup storage), // failover to Azurite local dev storage string awjs = Environment .GetEnvironmentVariable("AzureWebJobsStorage") @@ -35,7 +33,12 @@ public static async Task Startup(CancellationToken cancellationToken) } } - // SAVE BLOB + /// + /// Upload/save blob item (CSV quotes) + /// + /// Unique name of blob item + /// CSV payload to store + /// True/false success public static async Task PutBlob(string blobName, string csv) { BlobClient blob = GetBlobClient(blobName); @@ -49,7 +52,11 @@ public static async Task PutBlob(string blobName, string csv) return true; } - // HELPERS + /// + /// Get Azure Blob client + /// + /// Unique name of blob item + /// internal static BlobClient GetBlobClient(string blobName) { BlobContainerClient blobContainer @@ -61,6 +68,11 @@ BlobClient blob return blob; } + /// + /// Get blob storage container client + /// + /// Unique name of blob container (e.g. "chart-demo") + /// private static BlobContainerClient GetContainerClient(string containerName) { // failover to Azurite local dev storage diff --git a/Server/WebApi/Startup.cs b/Server/WebApi/Startup.cs index 1a58c4ad..c0854286 100644 --- a/Server/WebApi/Startup.cs +++ b/Server/WebApi/Startup.cs @@ -2,19 +2,19 @@ namespace WebApi.Services; public class StartupService : IHostedService { + /// + /// The code in here will run when the application starts, + /// and block the startup process until finished + /// + ///