├── .github └── workflows │ ├── build.yml │ └── codeql-analysis.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── LICENSE.md ├── README.md ├── backtesting-engine.sln ├── images ├── development_active.svg └── random-indices-sp500-variable.svg ├── scripts └── backtestings │ ├── ami.sh │ ├── aws.sh │ ├── experiments │ ├── momentum │ │ ├── forex │ │ │ ├── fixed │ │ │ └── momentum_14a2da93_focused │ │ ├── ftse100 │ │ └── indices │ │ │ └── fixed │ ├── random-hh-ll │ │ ├── bonds │ │ │ └── range │ │ ├── crypto │ │ │ └── range │ │ ├── forex │ │ │ └── range │ │ └── indices │ │ │ └── range │ ├── random │ │ ├── bonds │ │ │ ├── fixed │ │ │ ├── variable │ │ │ └── variable-larger │ │ ├── crypto │ │ │ └── variable │ │ ├── forex │ │ │ └── fixed │ │ └── indices │ │ │ └── fixed │ ├── strategies │ ├── trailing-sl │ │ ├── bonds │ │ │ └── range │ │ ├── crypto │ │ │ └── range │ │ ├── forex │ │ │ └── range │ │ └── indices │ │ │ └── range │ └── volatility │ │ ├── bonds │ │ └── fixed │ │ ├── crypto │ │ └── variable │ │ ├── forex │ │ └── fixed │ │ └── indices │ │ ├── fixed │ │ └── variable │ ├── run.sh │ ├── template.txt │ ├── variables │ └── defaults │ └── web.sh ├── src ├── Interfaces │ ├── IAccountObj.cs │ ├── ICloseOrder.cs │ ├── IConsumer.cs │ ├── IOpenOrder.cs │ ├── IOpenTrades.cs │ ├── IRequestOpenTrade.cs │ ├── IStrategy.cs │ ├── IStrategyObjects.cs │ ├── ISystemObject.cs │ ├── ISystemSetup.cs │ ├── ITaskManager.cs │ ├── ITradingObjects.cs │ ├── IWebNotification.cs │ └── interfaces.csproj ├── Models │ ├── AccountObj.cs │ ├── IEnvironmentVariables.cs │ ├── OHLCObject.cs │ ├── PriceObject.cs │ ├── RequetObject.cs │ ├── TradeDirection.cs │ ├── TradeHistoryObject.cs │ ├── VolatilityObject.cs │ └── models.csproj ├── backtesting │ ├── Abstracts │ │ └── TradingBase.cs │ ├── Analysis │ │ └── Reporting.cs │ ├── Consumer │ │ ├── Consumer.cs │ │ └── WebConsumer.cs │ ├── Exceptions │ │ └── TradingException.cs │ ├── Ingest │ │ └── Ingest.cs │ ├── Objects │ │ ├── InitialReport.cs │ │ ├── StrategyObjects.cs │ │ ├── SystemObjects.cs │ │ └── TradingObjects.cs │ ├── Operations │ │ ├── BacktestingOpenTrades.cs │ │ ├── Positions.cs │ │ └── RequestOpenTrade.cs │ ├── Program.cs │ ├── SystemSetup.cs │ ├── TaskManager.cs │ ├── TradeManagement │ │ ├── CloseOrder.cs │ │ └── OpenOrder.cs │ ├── Utilities │ │ ├── ParseDecimal.cs │ │ ├── ReportObj.cs │ │ ├── ShellHelper.cs │ │ └── StringFormats.cs │ ├── Web │ │ ├── EmptyNotification.cs │ │ └── WebNotification.cs │ └── backtesting-engine.csproj ├── live │ └── ingest │ │ ├── src │ │ ├── Function.cs │ │ ├── aws-lambda-tools-defaults.json │ │ └── ingest.csproj │ │ └── test │ │ ├── FunctionTest.cs │ │ └── ingest.Tests.csproj ├── strategies │ ├── BaseStrategy.cs │ ├── drafts │ │ ├── Hedging.cs │ │ ├── Momentum.cs │ │ ├── Momentum_438539dc_many.cs │ │ ├── Random-Extra.cs │ │ ├── RandomRecentHighLow copy.cs │ │ ├── RandomRecentHighLow_DRAFT.cs │ │ ├── SimpleMovingAverage.cs │ │ └── VolatilityCalculator.cs │ ├── locked │ │ ├── Momentum │ │ │ ├── Momentum_14a2da93.cs │ │ │ ├── Momentum_18d834cc.cs │ │ │ ├── Momentum_2ff0b2b2.cs │ │ │ ├── Momentum_438539dc.cs │ │ │ ├── Momentum_4d991e3b.cs │ │ │ ├── Momentum_59ca71ff.cs │ │ │ └── Momentum_7aa778ee.cs │ │ └── Random │ │ │ ├── Random.cs │ │ │ └── RandomRecentHighLow.cs │ └── strategies.csproj ├── ui │ ├── .gitignore │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ └── src │ │ ├── App.js │ │ ├── Functions │ │ ├── DisplayOHLCData.js │ │ ├── SaveClosedTrades.js │ │ └── TradesOnLinearXAxis.js │ │ ├── TradingUI │ │ └── Dashboard.js │ │ ├── index.css │ │ ├── index.js │ │ └── serviceWorker.js ├── utilities │ ├── ConsoleLogger.cs │ ├── DictionaryKeyStrings.cs │ ├── EnvironmentVariables.cs │ ├── GenericOHLC.cs │ ├── ServiceExtension.cs │ └── utilities.csproj ├── webserver │ ├── Controllers │ │ └── ChatController.cs │ ├── Hubs │ │ ├── ChatHub.cs │ │ └── Clients │ │ │ └── IChatClient.cs │ ├── Models │ │ └── ChatMessage.cs │ ├── Program.cs │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── webserver.csproj └── webutils │ ├── Program.cs │ └── webutils.csproj └── tests ├── backtesting ├── EnvironmentVariableTests.cs ├── IngestTests │ ├── DataInputTests.cs │ ├── EnvironmentTests.cs │ ├── ObjectsPopulateTest.cs │ └── ValidationTests.cs ├── ModelTests │ └── AccountObjTest.cs ├── OperationTests │ ├── PositionTests.cs │ └── RequestOpenTradeTests.cs ├── ReportingTests │ └── ReportingTests.cs ├── Shared.cs ├── StrategyTests │ └── RandomStrategyTests.cs ├── SystemTests │ └── SystemTests.cs ├── TestEnvironmentSetup.cs └── UtilitesTests │ └── ExtensionTests.cs ├── resources ├── EURUSD │ └── 2020.csv ├── TestEnvironmentSetup │ └── testSymbol.csv └── testSymbol.csv └── test.csproj /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | types: [opened, synchronize, reopened] 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: windows-latest 12 | steps: 13 | - uses: actions/setup-java@v4 14 | with: 15 | distribution: 'zulu' # See 'Supported distributions' for available options 16 | java-version: '21' 17 | - uses: actions/setup-dotnet@v4 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 21 | - name: Cache SonarCloud packages 22 | uses: actions/cache@v1 23 | with: 24 | path: ~\.sonar\cache 25 | key: ${{ runner.os }}-sonar 26 | restore-keys: ${{ runner.os }}-sonar 27 | - name: Cache SonarCloud scanner 28 | id: cache-sonar-scanner 29 | uses: actions/cache@v1 30 | with: 31 | path: .\.sonar\scanner 32 | key: ${{ runner.os }}-sonar-scanner 33 | restore-keys: ${{ runner.os }}-sonar-scanner 34 | - name: Install SonarCloud scanner 35 | if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' 36 | shell: powershell 37 | run: | 38 | New-Item -Path .\.sonar\scanner -ItemType Directory 39 | dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner 40 | - name: Test 41 | run: dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover 42 | - name: Begin 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 45 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 46 | shell: powershell 47 | run: .\.sonar\scanner\dotnet-sonarscanner begin /k:"mccaffers_backtesting-engine" /o:"mccaffers" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="./tests/coverage.opencover.xml" 48 | - name: Build 49 | run: dotnet build 50 | - name: Sonar End 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 53 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 54 | shell: powershell 55 | run: .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | 21 | jobs: 22 | analyze: 23 | name: Analyze 24 | runs-on: ubuntu-latest 25 | permissions: 26 | actions: read 27 | contents: read 28 | security-events: write 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | language: [ 'csharp' ] 34 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 35 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 36 | 37 | steps: 38 | 39 | - uses: actions/setup-dotnet@v1 40 | with: 41 | dotnet-version: 8.0.x 42 | 43 | - name: Checkout repository 44 | uses: actions/checkout@v2 45 | 46 | # Initializes the CodeQL tools for scanning. 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@v1 49 | with: 50 | languages: ${{ matrix.language }} 51 | # If you wish to specify custom queries, you can do so here or in a config file. 52 | # By default, queries listed here will override any specified in a config file. 53 | # Prefix the list here with "+" to use these queries and those in the config file. 54 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 https://git.io/JvXDl 63 | 64 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 65 | # and modify them (or add more) to build your code if your project 66 | # uses a compiled language 67 | 68 | # - name: build 69 | # run: dotnet build 70 | 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v1 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.*~ 3 | project.lock.json 4 | .DS_Store 5 | *.pyc 6 | nupkg/ 7 | 8 | # Rider 9 | .idea 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.userosscache 15 | *.sln.docstates 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | build/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Oo]ut/ 29 | msbuild.log 30 | msbuild.err 31 | msbuild.wrn 32 | 33 | # Visual Studio 2015 34 | .vs/ 35 | 36 | engine.zip 37 | tickdata/ 38 | scripts/data.sh 39 | src/ui/src/libs/ 40 | scripts/backtestings/data.sh -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | 4 | "configurations": [ 5 | { 6 | "name": "Chrome", 7 | "type": "chrome", 8 | "request": "attach", 9 | "url": "https://localhost:3000", // create-react-app's default port 3000 10 | "port": 9222, 11 | "webRoot": "${workspaceFolder}/src/ui", 12 | "preLaunchTask": "npm: start" // Add prelaunch Task npm: start (defined in tasks.json) 13 | }, 14 | { 15 | "name": "Trade via Console", 16 | "type": "coreclr", 17 | "request": "launch", 18 | "preLaunchTask": "build", 19 | "program": "${workspaceFolder}/src/backtesting/bin/Debug/net8.0/backtesting-engine.dll", 20 | "cwd": "${workspaceFolder}", 21 | "console": "internalConsole", 22 | "stopAtEntry": false, 23 | "envFile": "${workspaceFolder}/.env/local.env" 24 | }, 25 | { 26 | "name": "Trade via Web", 27 | "type": "coreclr", 28 | "request": "launch", 29 | "preLaunchTask": "build", 30 | "program": "${workspaceFolder}/src/backtesting/bin/Debug/net8.0/backtesting-engine.dll", 31 | "args": ["web"], 32 | "cwd": "${workspaceFolder}", 33 | "console": "internalConsole", 34 | "stopAtEntry": false, 35 | "envFile": "${workspaceFolder}/.env/local.env" 36 | }, 37 | { 38 | "name": ".NET Core Attach", 39 | "type": "coreclr", 40 | "request": "attach" 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/backtesting-engine.sln", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary;ForceNoAlign" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/backtesting-engine.sln", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary;ForceNoAlign" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/backtesting-engine.sln" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | }, 40 | { 41 | "type": "npm", 42 | "script": "start", 43 | "group": { 44 | "kind": "test", 45 | "isDefault": true 46 | }, 47 | "isBackground": true, // This prevents the launch.json to wait for the completion of the task 48 | "problemMatcher": { 49 | "owner": "custom", // This is not needed but, required by the problemMatcher Object 50 | "pattern": { 51 | "regexp": "^$" // This is not needed but, required by the problemMatcher Object 52 | }, 53 | "background": { 54 | "activeOnStart": true, 55 | "beginsPattern": "Compiling...", // Signals the begin of the Task 56 | "endsPattern": "Compiled .*" // Signals that now the initialization of the task is complete 57 | } 58 | }, 59 | "options": { 60 | "cwd": "${workspaceFolder}/src/ui" 61 | } 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ryan McCaffery 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## C# Backtesting Engine 2 | 3 | ### Active development 4 | 5 | Working build! Requires local tick data. Evaluate locally with `dotnet test` - 49 tests should pass, 1 skipped 6 | 7 | ## About The Project 8 | 9 | I'm developing a high-performance C# backtesting engine designed to analyze financial data and evaluate multiple trading strategies at scale. 10 | 11 | ![alt](images/development_active.svg) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mccaffers_backtesting-engine&metric=alert_status)](https://sonarcloud.io/summary/overall?id=mccaffers_backtesting-engine) [![Build](https://github.com/mccaffers/backtesting-engine/actions/workflows/build.yml/badge.svg)](https://github.com/mccaffers/backtesting-engine/actions/workflows/build.yml) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=mccaffers_backtesting-engine&metric=bugs)](https://sonarcloud.io/summary/new_code?id=mccaffers_backtesting-engine) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=mccaffers_backtesting-engine&metric=coverage)](https://sonarcloud.io/summary/new_code?id=mccaffers_backtesting-engine) 12 | 13 | 14 | I'm extracting results and creating various graphs for trend analyses using SciPy for calculations and Plotly for visualization. 15 | 16 | ![alt text](images/random-indices-sp500-variable.svg) 17 | 18 | *Read more results on https://mccaffers.com/randomly_trading/* 19 | 20 | ## Features 21 | 22 | - [x] Multiple symbol ingest with time synchronisation 23 | - [x] 50 xUnit testing across Trade Management, Ingest, Reporting & Utilities 24 | - [x] Trade Environment 25 | * Trade Excution 26 | * Equity Monitoring 27 | - [x] Reporting (ElasticSearch) 28 | 29 | ## Getting Started 30 | 31 | To begin with, ensure you can run the application with `dotnet test` 32 | 33 | **Local Terminal** 34 | 35 | sh ./scripts/backtesting/run.sh 36 | 37 | **Local Web & Terminal** (requires canvasjs.min.js) 38 | 39 | sh ./scripts/backtesting/web.sh 40 | 41 | ## Dependencies 42 | 43 | * dotnet v8 44 | * ElasticSearch for reporting 45 | * CanvasJS used for charting 46 | * JS libaries `canvasjs.min.js` to be placed within ./src/ui/src/libs/ folder 47 | * Charting & Web use, need to accept dotnet's certificates `dotnet dev-certs https --trust` 48 | * Some financial tick data in CSV format, in the /src/tickdata folder (example provided) 49 | 50 | ```bash 51 | # ./tickdata/{symbol}/2020.csv: 52 | UTC,AskPrice,BidPrice,AskVolume,BidVolume 53 | 2018-01-01T01:00:00.594+00:00,1.35104,1.35065,1.5,0.75 54 | ``` 55 | 56 | ## Debugging 57 | 58 | If you have changed target frameworks, make sure to update the program path in .vscode `launch.json` 59 | 60 | ## Random Trading Strategy 61 | accountEquity=0 62 | maximumDrawndownPercentage=0 63 | 64 | ## License 65 | [MIT](https://choosealicense.com/licenses/mit/) 66 | -------------------------------------------------------------------------------- /images/development_active.svg: -------------------------------------------------------------------------------- 1 | 2 | development: active 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /scripts/backtestings/ami.sh: -------------------------------------------------------------------------------- 1 | ### 2 | ### Script to setup AL2023 Image 3 | ### 4 | 5 | ## Install dependencies to unpack tick files and install .NET 8 6 | sudo yum install -y zstd libicu 7 | 8 | ## Setup dotnet 9 | wget -q https://dot.net/v1/dotnet-install.sh 10 | sh dotnet-install.sh -Channel 8.0 11 | ln -s /root/.dotnet/dotnet /usr/bin/dotnet 12 | 13 | ## Free up some space in the image 14 | sudo yum clean all -y 15 | sudo find /var -name "*.log" \( \( -size +50M -mtime +7 \) -o -mtime +30 \) -exec truncate {} --size 0 \; 16 | sudo rm -rf /var/cache/yum -------------------------------------------------------------------------------- /scripts/backtestings/experiments/momentum/forex/fixed: -------------------------------------------------------------------------------- 1 | declare -a strategies=("Momentum_4d991e3b") 2 | declare -a symbolsArray=("EURUSD" "USDJPY" "GBPUSD" "NZDUSD" "USDCHF" "USDCAD" "AUDUSD") 3 | 4 | # Forex from 2004 5 | yearsStart=2018 6 | yearsEnd=2023 7 | 8 | # These don't mean anything in RandomHHLL as the SL/TP is defined by the highest and lowest value from the last time period 9 | stopLossInPipsRange="200 1 200" # STOP LOSS Distance in PIPs 10 | limitInPipsRange="200 1 200" # TAKE PROFIT Distance in PIPs 11 | 12 | # Account Equity 13 | accountEquity=10000 14 | maximumDrawndownPercentage=75 15 | fasterProcessingBySkippingSomeTickData=true 16 | 17 | # Strategy Variables 18 | variableA_range="5 5 15" -------------------------------------------------------------------------------- /scripts/backtestings/experiments/momentum/forex/momentum_14a2da93_focused: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | declare -a strategies=("Momentum_4d991e3b") 3 | # declare -a symbolsArray=("EURUSD" "USDJPY" "GBPUSD" "NZDUSD" "USDCHF" "USDCAD" "AUDUSD") 4 | declare -a symbolsArray=("GBPUSD" "NZDUSD" "USDCAD" "AUDUSD") 5 | 6 | # Forex from 2004 7 | yearsStart=2004 8 | yearsEnd=2023 9 | 10 | # These don't mean anything in RandomHHLL as the SL/TP is defined by the highest and lowest value from the last time period 11 | stopLossInPipsRange="200 1 200" # STOP LOSS Distance in PIPs 12 | limitInPipsRange="200 1 200" # TAKE PROFIT Distance in PIPs 13 | 14 | # Account Equity 15 | accountEquity=10000 16 | maximumDrawndownPercentage=75 17 | fasterProcessingBySkippingSomeTickData=true 18 | 19 | # Strategy Variables 20 | variableA_range="5 5 15" -------------------------------------------------------------------------------- /scripts/backtestings/experiments/momentum/ftse100: -------------------------------------------------------------------------------- 1 | declare -a strategies=("Momentum") 2 | 3 | ## Multiple Indexes 4 | declare -a symbolsArray=("GBRIDXGBP") 5 | 6 | # Forex from 2004 7 | yearsStart=2014 8 | yearsEnd=2023 9 | 10 | # STOP LOSS Distance in PIPs 11 | stopLossInPipsRange="100 100 1000" 12 | 13 | # TAKE PROFIT Distance in PIPs 14 | limitInPipsRange="100 100 1000" 15 | 16 | # Account Equity 17 | accountEquity=500 18 | maximumDrawndownPercentage=50 19 | fasterProcessingBySkippingSomeTickData=true -------------------------------------------------------------------------------- /scripts/backtestings/experiments/momentum/indices/fixed: -------------------------------------------------------------------------------- 1 | declare -a strategies=("Momentum_2ff0b2b2") 2 | 3 | ## Multiple Indexes 4 | declare -a symbolsArray=("JPNIDXJPY" "ESPIDXEUR" "FRAIDXEUR" "DEUIDXEUR" "AUSIDXAUD" "USATECHIDXUSD" "USA500IDXUSD" "USA30IDXUSD" "EUSIDXEUR" "GBRIDXGBP") 5 | 6 | # Forex from 2004 7 | yearsStart=2018 8 | yearsEnd=2023 9 | 10 | # These don't mean anything in RandomHHLL as the SL/TP is defined by the highest and lowest value from the last time period 11 | stopLossInPipsRange="200 1 200" # STOP LOSS Distance in PIPs 12 | limitInPipsRange="200 1 200" # TAKE PROFIT Distance in PIPs 13 | 14 | # Account Equity 15 | accountEquity=10000 16 | maximumDrawndownPercentage=75 17 | fasterProcessingBySkippingSomeTickData=true 18 | 19 | # Strategy Variables 20 | variableA_range="5 5 15" -------------------------------------------------------------------------------- /scripts/backtestings/experiments/random-hh-ll/bonds/range: -------------------------------------------------------------------------------- 1 | declare -a strategies=("RandomRecentHighLow") 2 | 3 | declare -a symbolsArray=("BUNDTREUR" "UKGILTTRGBP" "USTBONDTRUSD") 4 | 5 | # Forex from 2004 6 | yearsStart=2020 7 | yearsEnd=2023 8 | 9 | # These don't mean anything in RandomHHLL as the SL/TP is defined by the highest and lowest value from the last time period 10 | stopLossInPipsRange="200 1 200" # STOP LOSS Distance in PIPs 11 | limitInPipsRange="200 1 200" # TAKE PROFIT Distance in PIPs 12 | 13 | # Account Equity 14 | accountEquity=10000 15 | maximumDrawndownPercentage=75 16 | fasterProcessingBySkippingSomeTickData=true 17 | 18 | # Strategy Variables 19 | variableA_range="4 2 10" 20 | #RANDOMHHLL_HOURS_TO_KEEP=5 21 | #RANDOMHHLL_TP_LOOKBACK_OVER_HOURS=2 -------------------------------------------------------------------------------- /scripts/backtestings/experiments/random-hh-ll/crypto/range: -------------------------------------------------------------------------------- 1 | declare -a strategies=("RandomRecentHighLow") 2 | 3 | ## Crypto 4 | declare -a symbolsArray=("ETHUSD" "BTCUSD") 5 | 6 | # Forex from 2004 7 | yearsStart=2018 8 | yearsEnd=2023 9 | 10 | # These don't mean anything in RandomHHLL as the SL/TP is defined by the highest and lowest value from the last time period 11 | stopLossInPipsRange="200 1 200" # STOP LOSS Distance in PIPs 12 | limitInPipsRange="200 1 200" # TAKE PROFIT Distance in PIPs 13 | 14 | # Account Equity 15 | accountEquity=10000 16 | maximumDrawndownPercentage=75 17 | fasterProcessingBySkippingSomeTickData=true 18 | 19 | # Strategy Variables 20 | variableA_range="4 2 10" 21 | #RANDOMHHLL_HOURS_TO_KEEP=5 22 | #RANDOMHHLL_TP_LOOKBACK_OVER_HOURS=2 -------------------------------------------------------------------------------- /scripts/backtestings/experiments/random-hh-ll/forex/range: -------------------------------------------------------------------------------- 1 | declare -a strategies=("RandomRecentHighLow") 2 | declare -a symbolsArray=("EURUSD" "USDJPY" "GBPUSD" "NZDUSD" "USDCHF" "USDCAD" "AUDUSD") 3 | 4 | # Forex from 2004 5 | yearsStart=2018 6 | yearsEnd=2023 7 | 8 | # These don't mean anything in RandomHHLL as the SL/TP is defined by the highest and lowest value from the last time period 9 | stopLossInPipsRange="200 1 200" # STOP LOSS Distance in PIPs 10 | limitInPipsRange="200 1 200" # TAKE PROFIT Distance in PIPs 11 | 12 | # Account Equity 13 | accountEquity=10000 14 | maximumDrawndownPercentage=75 15 | fasterProcessingBySkippingSomeTickData=true 16 | 17 | # Strategy Variables 18 | variableA_range="4 2 10" 19 | #RANDOMHHLL_HOURS_TO_KEEP=5 20 | #RANDOMHHLL_TP_LOOKBACK_OVER_HOURS=2 -------------------------------------------------------------------------------- /scripts/backtestings/experiments/random-hh-ll/indices/range: -------------------------------------------------------------------------------- 1 | declare -a strategies=("RandomRecentHighLow") 2 | 3 | ## Multiple Indexes 4 | declare -a symbolsArray=("JPNIDXJPY" "ESPIDXEUR" "FRAIDXEUR" "DEUIDXEUR" "AUSIDXAUD" "USATECHIDXUSD" "USA500IDXUSD" "USA30IDXUSD" "EUSIDXEUR" "GBRIDXGBP") 5 | 6 | # Forex from 2004 7 | yearsStart=2014 8 | yearsEnd=2023 9 | 10 | # These don't mean anything in RandomHHLL as the SL/TP is defined by the highest and lowest value from the last time period 11 | stopLossInPipsRange="200 1 200" # STOP LOSS Distance in PIPs 12 | limitInPipsRange="200 1 200" # TAKE PROFIT Distance in PIPs 13 | 14 | # Account Equity 15 | accountEquity=10000 16 | maximumDrawndownPercentage=75 17 | fasterProcessingBySkippingSomeTickData=true 18 | 19 | # Strategy Variables 20 | variableA_range="4 2 10" 21 | #RANDOMHHLL_HOURS_TO_KEEP=5 22 | #RANDOMHHLL_TP_LOOKBACK_OVER_HOURS=2 -------------------------------------------------------------------------------- /scripts/backtestings/experiments/random/bonds/fixed: -------------------------------------------------------------------------------- 1 | declare -a strategies=("RandomStrategy") 2 | declare -a symbolsArray=("BUNDTREUR" "UKGILTTRGBP" "USTBONDTRUSD") 3 | 4 | # Forex from 2004 5 | yearsStart=2020 6 | yearsEnd=2023 7 | 8 | # STOP LOSS Distance in PIPs 9 | stopLossInPipsRange="30 1 30" 10 | 11 | # TAKE PROFIT Distance in PIPs 12 | limitInPipsRange="30 20 30" 13 | 14 | # Account Equity 15 | accountEquity=0 16 | maximumDrawndownPercentage=0 17 | fasterProcessingBySkippingSomeTickData=true -------------------------------------------------------------------------------- /scripts/backtestings/experiments/random/bonds/variable: -------------------------------------------------------------------------------- 1 | declare -a strategies=("RandomStrategy") 2 | declare -a symbolsArray=("BUNDTREUR" "UKGILTTRGBP" "USTBONDTRUSD") 3 | 4 | # Forex from 2004 5 | yearsStart=2020 6 | yearsEnd=2023 7 | 8 | # STOP LOSS Distance in PIPs 9 | stopLossInPipsRange="20 20 100" 10 | 11 | # TAKE PROFIT Distance in PIPs 12 | limitInPipsRange="20 20 100" 13 | 14 | # Account Equity 15 | accountEquity=10000 16 | maximumDrawndownPercentage=75 17 | fasterProcessingBySkippingSomeTickData=true -------------------------------------------------------------------------------- /scripts/backtestings/experiments/random/bonds/variable-larger: -------------------------------------------------------------------------------- 1 | declare -a strategies=("RandomStrategy") 2 | declare -a symbolsArray=("BUNDTREUR" "UKGILTTRGBP" "USTBONDTRUSD") 3 | 4 | # Forex from 2004 5 | yearsStart=2020 6 | yearsEnd=2023 7 | 8 | # STOP LOSS Distance in PIPs 9 | stopLossInPipsRange="100 20 200" 10 | 11 | # TAKE PROFIT Distance in PIPs 12 | limitInPipsRange="100 20 200" 13 | 14 | # Account Equity 15 | accountEquity=10000 16 | maximumDrawndownPercentage=75 17 | fasterProcessingBySkippingSomeTickData=true -------------------------------------------------------------------------------- /scripts/backtestings/experiments/random/crypto/variable: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -a strategies=("RandomStrategy") 4 | 5 | ## Crypto 6 | declare -a symbolsArray=("ETHUSD" "BTCUSD") 7 | yearsStart=2018 8 | yearsEnd=2023 9 | 10 | # STOP LOSS Distance in PIPs 11 | stopLossInPipsRange="20 20 100" 12 | 13 | # TAKE PROFIT Distance in PIPs 14 | limitInPipsRange="20 20 100" 15 | 16 | # Account Equity 17 | accountEquity=10000 18 | maximumDrawndownPercentage=75 19 | fasterProcessingBySkippingSomeTickData=true -------------------------------------------------------------------------------- /scripts/backtestings/experiments/random/forex/fixed: -------------------------------------------------------------------------------- 1 | declare -a strategies=("RandomStrategy") 2 | declare -a symbolsArray=("EURUSD" "USDJPY" "GBPUSD" "NZDUSD" "USDCHF" "USDCAD" "AUDUSD") 3 | 4 | # Forex from 2004 5 | yearsStart=2004 6 | yearsEnd=2023 7 | 8 | # STOP LOSS Distance in PIPs 9 | stopLossInPipsRange="30 1 30" 10 | 11 | # TAKE PROFIT Distance in PIPs 12 | limitInPipsRange="30 20 30" 13 | 14 | # Account Equity 15 | accountEquity=0 16 | maximumDrawndownPercentage=0 17 | fasterProcessingBySkippingSomeTickData=true -------------------------------------------------------------------------------- /scripts/backtestings/experiments/random/indices/fixed: -------------------------------------------------------------------------------- 1 | declare -a strategies=("RandomStrategy") 2 | 3 | ## Multiple Indexes 4 | declare -a symbolsArray=("JPNIDXJPY" "ESPIDXEUR" "FRAIDXEUR" "DEUIDXEUR" "AUSIDXAUD" "USATECHIDXUSD" "USA500IDXUSD" "USA30IDXUSD" "EUSIDXEUR" "GBRIDXGBP") 5 | 6 | # Forex from 2004 7 | yearsStart=2014 8 | yearsEnd=2023 9 | 10 | # STOP LOSS Distance in PIPs 11 | stopLossInPipsRange="30 1 30" 12 | 13 | # TAKE PROFIT Distance in PIPs 14 | limitInPipsRange="30 20 30" 15 | 16 | # Account Equity 17 | accountEquity=0 18 | maximumDrawndownPercentage=0 19 | fasterProcessingBySkippingSomeTickData=true -------------------------------------------------------------------------------- /scripts/backtestings/experiments/strategies: -------------------------------------------------------------------------------- 1 | VolatilityCalculator 2 | RandomStrategy 3 | RandomWithCloseAtHHLL -------------------------------------------------------------------------------- /scripts/backtestings/experiments/trailing-sl/bonds/range: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -a strategies=("RandomStrategy") 4 | 5 | declare -a symbolsArray=("BUNDTREUR" "UKGILTTRGBP" "USTBONDTRUSD") 6 | 7 | # Forex from 2004 8 | yearsStart=2020 9 | yearsEnd=2023 10 | 11 | # STOP LOSS Distance in PIPs 12 | stopLossInPipsRange="100 1 100" 13 | 14 | # TAKE PROFIT Distance in PIPs 15 | limitInPipsRange="100 1 100" 16 | 17 | # Account Equity 18 | accountEquity=10000 19 | maximumDrawndownPercentage=75 20 | fasterProcessingBySkippingSomeTickData=true 21 | 22 | # Trailing SL and moving TP 23 | kineticStopLossRange="20 20 100" 24 | kineticLimitRange="1 1 1" -------------------------------------------------------------------------------- /scripts/backtestings/experiments/trailing-sl/crypto/range: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -a strategies=("RandomStrategy") 4 | 5 | ## Crypto 6 | declare -a symbolsArray=("ETHUSD" "BTCUSD") 7 | 8 | # Forex from 2004 9 | yearsStart=2018 10 | yearsEnd=2023 11 | 12 | # STOP LOSS Distance in PIPs 13 | stopLossInPipsRange="100 1 100" 14 | 15 | # TAKE PROFIT Distance in PIPs 16 | limitInPipsRange="100 1 100" 17 | 18 | # Account Equity 19 | accountEquity=10000 20 | maximumDrawndownPercentage=75 21 | fasterProcessingBySkippingSomeTickData=true 22 | 23 | # Trailing SL and moving TP 24 | kineticStopLossRange="100 20 200" 25 | kineticLimitRange="1 1 1" -------------------------------------------------------------------------------- /scripts/backtestings/experiments/trailing-sl/forex/range: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -a strategies=("RandomStrategy") 4 | 5 | ## Multiple Currencies 6 | declare -a symbolsArray=("EURUSD" "USDJPY" "GBPUSD" "NZDUSD" "USDCHF" "USDCAD" "AUDUSD") 7 | 8 | # Forex from 2004 9 | yearsStart=2004 10 | yearsEnd=2023 11 | 12 | # STOP LOSS Distance in PIPs 13 | stopLossInPipsRange="100 1 100" 14 | 15 | # TAKE PROFIT Distance in PIPs 16 | limitInPipsRange="100 1 100" 17 | 18 | # Account Equity 19 | accountEquity=10000 20 | maximumDrawndownPercentage=75 21 | fasterProcessingBySkippingSomeTickData=true 22 | 23 | # Trailing SL and moving TP 24 | kineticStopLossRange="100 20 200" 25 | kineticLimitRange="1 1 1" -------------------------------------------------------------------------------- /scripts/backtestings/experiments/trailing-sl/indices/range: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -a strategies=("RandomStrategy") 4 | 5 | ## Multiple Indexes 6 | declare -a symbolsArray=("JPNIDXJPY" "ESPIDXEUR" "FRAIDXEUR" "DEUIDXEUR" "AUSIDXAUD" "USATECHIDXUSD" "USA500IDXUSD" "USA30IDXUSD" "EUSIDXEUR" "GBRIDXGBP") 7 | 8 | # Forex from 2004 9 | yearsStart=2014 10 | yearsEnd=2023 11 | 12 | # STOP LOSS Distance in PIPs 13 | stopLossInPipsRange="100 1 100" 14 | 15 | # TAKE PROFIT Distance in PIPs 16 | limitInPipsRange="100 1 100" 17 | 18 | # Account Equity 19 | accountEquity=10000 20 | maximumDrawndownPercentage=75 21 | fasterProcessingBySkippingSomeTickData=true 22 | 23 | # Trailing SL and moving TP 24 | kineticStopLossRange="100 20 200" 25 | kineticLimitRange="1 1 1" -------------------------------------------------------------------------------- /scripts/backtestings/experiments/volatility/bonds/fixed: -------------------------------------------------------------------------------- 1 | declare -a strategies=("RandomStrategy") 2 | declare -a symbolsArray=("BUNDTREUR" "UKGILTTRGBP" "USTBONDTRUSD") 3 | 4 | # Forex from 2004 5 | yearsStart=2020 6 | yearsEnd=2023 7 | 8 | # STOP LOSS Distance in PIPs 9 | stopLossInPipsRange="30 1 30" 10 | 11 | # TAKE PROFIT Distance in PIPs 12 | limitInPipsRange="30 20 30" 13 | 14 | # Account Equity 15 | accountEquity=0 16 | maximumDrawndownPercentage=0 17 | fasterProcessingBySkippingSomeTickData=true -------------------------------------------------------------------------------- /scripts/backtestings/experiments/volatility/crypto/variable: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -a strategies=("RandomStrategy") 4 | 5 | ## Crypto 6 | declare -a symbolsArray=("ETHUSD" "BTCUSD") 7 | yearsStart=2018 8 | yearsEnd=2023 9 | 10 | # STOP LOSS Distance in PIPs 11 | stopLossInPipsRange="20 20 100" 12 | 13 | # TAKE PROFIT Distance in PIPs 14 | limitInPipsRange="20 20 100" 15 | 16 | # Account Equity 17 | accountEquity=10000 18 | maximumDrawndownPercentage=75 19 | fasterProcessingBySkippingSomeTickData=true -------------------------------------------------------------------------------- /scripts/backtestings/experiments/volatility/forex/fixed: -------------------------------------------------------------------------------- 1 | declare -a strategies=("VolatilityCalculator") 2 | declare -a symbolsArray=("NZDUSD") 3 | 4 | # Forex from 2004 5 | yearsStart=2021 6 | yearsEnd=2023 7 | 8 | # STOP LOSS Distance in PIPs 9 | stopLossInPipsRange="30 1 30" 10 | 11 | # TAKE PROFIT Distance in PIPs 12 | limitInPipsRange="30 20 30" 13 | 14 | # Account Equity 15 | accountEquity=0 16 | maximumDrawndownPercentage=0 17 | fasterProcessingBySkippingSomeTickData=true -------------------------------------------------------------------------------- /scripts/backtestings/experiments/volatility/indices/fixed: -------------------------------------------------------------------------------- 1 | declare -a strategies=("RandomStrategy") 2 | 3 | ## Multiple Indexes 4 | declare -a symbolsArray=("JPNIDXJPY" "ESPIDXEUR" "FRAIDXEUR" "DEUIDXEUR" "AUSIDXAUD" "USATECHIDXUSD" "USA500IDXUSD" "USA30IDXUSD" "EUSIDXEUR" "GBRIDXGBP") 5 | 6 | # Forex from 2004 7 | yearsStart=2014 8 | yearsEnd=2023 9 | 10 | # STOP LOSS Distance in PIPs 11 | stopLossInPipsRange="30 1 30" 12 | 13 | # TAKE PROFIT Distance in PIPs 14 | limitInPipsRange="30 20 30" 15 | 16 | # Account Equity 17 | accountEquity=0 18 | maximumDrawndownPercentage=0 19 | fasterProcessingBySkippingSomeTickData=true -------------------------------------------------------------------------------- /scripts/backtestings/experiments/volatility/indices/variable: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -a strategies=("RandomStrategy") 4 | 5 | ## Multiple Indexes 6 | declare -a symbolsArray=("JPNIDXJPY" "ESPIDXEUR" "FRAIDXEUR" "DEUIDXEUR" "AUSIDXAUD" "USATECHIDXUSD" "USA500IDXUSD" "USA30IDXUSD" "EUSIDXEUR" "GBRIDXGBP") 7 | 8 | # Forex from 2004 9 | yearsStart=2014 10 | yearsEnd=2023 11 | 12 | # STOP LOSS Distance in PIPs 13 | stopLossInPipsRange="20 20 100" 14 | 15 | # TAKE PROFIT Distance in PIPs 16 | limitInPipsRange="20 20 100" 17 | 18 | # Account Equity 19 | accountEquity=10000 20 | maximumDrawndownPercentage=75 21 | fasterProcessingBySkippingSomeTickData=true -------------------------------------------------------------------------------- /scripts/backtestings/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | set -o allexport 5 | source ./.env/local.env 6 | set +o allexport 7 | 8 | runID=$(uuidgen|sed -e 's/-//g') 9 | 10 | dotnet build ./src/backtesting 11 | # dotnet test 12 | 13 | echo $runID 14 | dotnet run --project ./src/backtesting -------------------------------------------------------------------------------- /scripts/backtestings/template.txt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | { 3 | export symbols=${symbols} 4 | export runID=${runID} 5 | export stopDistanceInPips=${stopDistanceInPips} 6 | export limitDistanceInPips=${limitDistanceInPips} 7 | export symbolFolder=${symbolFolder} 8 | export scalingFactor="${scalingFactor}" 9 | export accountEquity=${accountEquity} 10 | export maximumDrawndownPercentage=${maximumDrawndownPercentage} 11 | export strategy=${strategy} 12 | export reportingEnabled=true 13 | export s3Bucket="${s3Bucket}" 14 | export s3Path=${s3Path} 15 | export yearsStart=${yearsStart} 16 | export yearsEnd=${yearsEnd} 17 | export elasticUser=${elasticUser} 18 | export elasticPassword="${elasticPassword}" 19 | export elasticEndpoint="${elasticEndpoint}" 20 | export elasticCloudID="${elasticCloudID}" 21 | export runIteration=${runIteration} 22 | export tradingSize=${tradingSize} 23 | export instanceCount=${instanceCount} 24 | export kineticStopLoss=${kineticStopLoss} 25 | export kineticLimit=${kineticLimit} 26 | export operatingEnvironment=${operatingEnvironment} 27 | export fasterProcessingBySkippingSomeTickData=${fasterProcessingBySkippingSomeTickData} 28 | export variableA=${variableA} 29 | export variableB=${variableB} 30 | export variableC=${variableC} 31 | export variableD=${variableD} 32 | export variableE=${variableE} 33 | 34 | export consoleLog=false 35 | export systemLog=false 36 | 37 | export DOTNET_CLI_TELEMETRY_OPTOUT=1 38 | 39 | # Pull the project files 40 | aws s3api get-object --bucket ${awsDeployBucket} --key run/${runID}.zip /home/ec2-user/files.zip 41 | 42 | # Setup dotnet home 43 | export HOME=/root/ 44 | 45 | # Extract project files 46 | cd /home/ec2-user/ 47 | mkdir /home/ec2-user/project 48 | unzip files.zip -d ./project 49 | rm -rf ./files.zip 50 | 51 | # Run project 52 | dotnet build /home/ec2-user/project -v q 53 | dotnet run --project /home/ec2-user/project/src/backtesting 54 | poweroff 55 | } &> /home/ec2-user/output.txt -------------------------------------------------------------------------------- /scripts/backtestings/variables/defaults: -------------------------------------------------------------------------------- 1 | ########## 2 | ########## 3 | # Trading Defaults 4 | # Do not change here 5 | 6 | stopLossInPipsRange="1 1 1" 7 | limitInPipsRange="1 1 1" 8 | iterationRange="1 1 1" 9 | yearsStart=0 10 | yearsEnd=0 11 | accountEquity=0 12 | maximumDrawndownPercentage=0 # zero run forever 13 | kineticOn=0 # 1 on, 0 off 14 | kineticStopLossRange="0 1 0" 15 | kineticLimitRange="0 1 0" 16 | tradingSize=1 17 | fasterProcessingBySkippingSomeTickData=false 18 | 19 | ########## 20 | ########## -------------------------------------------------------------------------------- /scripts/backtestings/web.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | set -o allexport 5 | source ./.env/local.env 6 | set +o allexport 7 | 8 | runID=$(uuidgen|sed -e 's/-//g') 9 | # echo $runID 10 | 11 | dotnet build ./src/backtesting 12 | 13 | ## Function to kill any running dotnet processes 14 | ## when the script closes 15 | _term() { 16 | # Ensure dotnet exits 17 | echo "pkill -f './src/backtesting'" 18 | pkill -f "./src/backtesting"; echo $? 19 | echo """EXIT STATUS - (pkill dotnet) 20 | 0 One or more processes matched the criteria. 21 | 1 No processes matched""" 22 | } 23 | 24 | ## Ensure all previous dotnet processes 25 | ## have closed before starting a new one 26 | _term || true 27 | 28 | trap _term SIGINT SIGTERM 29 | 30 | ## Ensure all of the dependencies in the UI folder 31 | ## have been installed 32 | npm install --prefix ./src/ui 33 | 34 | ## Use gnu parallel to launch both processes together 35 | ## and half if either one `fail=1` was to close 36 | parallel --halt now,fail=1 --line-buffer --tty --jobs 2 --verbose ::: \ 37 | "dotnet run --project ./src/backtesting -- web" \ 38 | "npm start --prefix ./src/ui/ 1> /dev/null 2> /dev/null" 39 | 40 | -------------------------------------------------------------------------------- /src/Interfaces/IAccountObj.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine_models; 2 | 3 | namespace backtesting_engine.interfaces; 4 | 5 | public interface IAccountObj 6 | { 7 | decimal openingEquity { get; init; } 8 | decimal maximumDrawndownPercentage { get; init; } 9 | decimal tradeHistorySum { get; } 10 | decimal pnl { get; } 11 | 12 | void AddTradeProftOrLoss(decimal input); 13 | decimal CalculateProfit(decimal level, RequestObject openTradeObj); 14 | bool hasAccountExceededDrawdownThreshold(); 15 | } -------------------------------------------------------------------------------- /src/Interfaces/ICloseOrder.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine_models; 2 | 3 | namespace backtesting_engine.interfaces; 4 | 5 | public interface ICloseOrder 6 | { 7 | void Request(RequestObject reqObj, PriceObj priceObj, bool completed); 8 | void Request(RequestObject reqObj, PriceObj priceObj); 9 | void PushRequest(PriceObj priceObj); 10 | } 11 | -------------------------------------------------------------------------------- /src/Interfaces/IConsumer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks.Dataflow; 2 | 3 | namespace backtesting_engine.interfaces; 4 | 5 | public interface IConsumer 6 | { 7 | Task ConsumeAsync(BufferBlock buffer, CancellationToken cts); 8 | } -------------------------------------------------------------------------------- /src/Interfaces/IOpenOrder.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine_models; 2 | 3 | namespace backtesting_engine.interfaces; 4 | 5 | public interface IOpenOrder 6 | { 7 | void Request(RequestObject reqObj); 8 | } -------------------------------------------------------------------------------- /src/Interfaces/IOpenTrades.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine_models; 2 | 3 | namespace backtesting_engine.interfaces; 4 | 5 | public interface IOpenTrades 6 | { 7 | Task Request(string symbol); 8 | } -------------------------------------------------------------------------------- /src/Interfaces/IRequestOpenTrade.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine_models; 2 | 3 | namespace backtesting_engine.interfaces; 4 | 5 | public interface IRequestOpenTrade 6 | { 7 | Task Request(RequestObject reqObj); 8 | } -------------------------------------------------------------------------------- /src/Interfaces/IStrategy.cs: -------------------------------------------------------------------------------- 1 | namespace backtesting_engine.interfaces; 2 | 3 | public interface IStrategy 4 | { 5 | Task Invoke(PriceObj priceObj); 6 | } -------------------------------------------------------------------------------- /src/Interfaces/IStrategyObjects.cs: -------------------------------------------------------------------------------- 1 | namespace backtesting_engine.interfaces; 2 | 3 | public interface IStrategyObjects 4 | { 5 | Task> GetOHLCObject(PriceObj priceObj, decimal price, TimeSpan duration, List OHLCArray); 6 | Task GetLastTraded(PriceObj priceObj); 7 | Task SetLastTraded(PriceObj priceObj); 8 | } -------------------------------------------------------------------------------- /src/Interfaces/ISystemObject.cs: -------------------------------------------------------------------------------- 1 | namespace backtesting_engine.interfaces; 2 | 3 | public interface ISystemObjects 4 | { 5 | DateTime systemStartTime { get; } 6 | string systemMessage { get; set; } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/Interfaces/ISystemSetup.cs: -------------------------------------------------------------------------------- 1 | namespace backtesting_engine.interfaces; 2 | 3 | public interface ISystemSetup 4 | { 5 | Task SendStackException(Exception ex); 6 | Task StartEngine(); 7 | } 8 | -------------------------------------------------------------------------------- /src/Interfaces/ITaskManager.cs: -------------------------------------------------------------------------------- 1 | namespace backtesting_engine.interfaces; 2 | 3 | public interface ITaskManager { 4 | Task IngestAndConsume(); 5 | } -------------------------------------------------------------------------------- /src/Interfaces/ITradingObjects.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using backtesting_engine_models; 3 | 4 | namespace backtesting_engine.interfaces; 5 | 6 | public interface ITradingObjects 7 | { 8 | ConcurrentDictionary openTrades { get; } 9 | ConcurrentDictionary tradeHistory { get; } 10 | string test { get; set; } 11 | DateTime tradeTime { get; set; } 12 | AccountObj accountObj {get; init;} 13 | } -------------------------------------------------------------------------------- /src/Interfaces/IWebNotification.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine; 2 | using backtesting_engine_models; 3 | 4 | namespace backtesting_engine.interfaces; 5 | 6 | public interface IWebNotification 7 | { 8 | Task PriceUpdate(OhlcObject input, bool force = false); 9 | Task AccountUpdate(decimal input); 10 | Task OpenTrades(RequestObject input); 11 | Task TradeUpdate(TradeHistoryObject input); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/Interfaces/interfaces.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Models/AccountObj.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using backtesting_engine.interfaces; 3 | using backtesting_engine_models; 4 | 5 | namespace backtesting_engine; 6 | 7 | public interface IAccountObj 8 | { 9 | decimal openingEquity { get; init; } 10 | decimal maximumDrawndownPercentage { get; init; } 11 | decimal tradeHistorySum { get; } 12 | decimal pnl { get; } 13 | 14 | void AddTradeProftOrLoss(decimal input); 15 | decimal CalculateProfit(decimal level, RequestObject openTradeObj); 16 | bool hasAccountExceededDrawdownThreshold(); 17 | } 18 | 19 | public class AccountObj : IAccountObj 20 | { 21 | readonly ConcurrentDictionary openTrades; 22 | 23 | public decimal openingEquity { get; init; } 24 | public decimal maximumDrawndownPercentage { get; init; } 25 | public decimal tradeHistorySum { get; private set; } = decimal.Zero; 26 | 27 | readonly IEnvironmentVariables envVariables; 28 | 29 | public AccountObj(ConcurrentDictionary openTrades, 30 | ConcurrentDictionary tradeHistory, 31 | decimal openingEquity, 32 | decimal maximumDrawndownPercentage, 33 | IEnvironmentVariables envVariables) 34 | { 35 | 36 | this.openTrades = openTrades; 37 | this.openingEquity = openingEquity; 38 | this.maximumDrawndownPercentage = maximumDrawndownPercentage; 39 | this.envVariables = envVariables; 40 | } 41 | 42 | public void AddTradeProftOrLoss(decimal input) 43 | { 44 | this.tradeHistorySum += input; 45 | } 46 | 47 | public decimal pnl 48 | { 49 | get 50 | { 51 | return this.openingEquity + this.tradeHistorySum + openTrades.Sum(x => CalculateProfit(x.Value.closeLevel, x.Value)); 52 | } 53 | } 54 | 55 | public decimal CalculateProfit(decimal level, RequestObject openTradeObj) 56 | { 57 | var difference = openTradeObj.direction == TradeDirection.BUY ? level - openTradeObj.level : openTradeObj.level - level; 58 | return difference * this.envVariables.GetScalingFactor(openTradeObj.symbol) * openTradeObj.size; 59 | } 60 | 61 | public bool hasAccountExceededDrawdownThreshold() 62 | { 63 | if (this.maximumDrawndownPercentage == 0) 64 | { 65 | return false; 66 | } 67 | return (this.pnl < this.openingEquity * (1 - (this.maximumDrawndownPercentage / 100))); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/Models/IEnvironmentVariables.cs: -------------------------------------------------------------------------------- 1 | namespace backtesting_engine.interfaces; 2 | 3 | public interface IEnvironmentVariables 4 | { 5 | string operatingEnvironment { get; init; } 6 | 7 | // Both 8 | string strategy { get; set; } 9 | string runID { get; init; } 10 | string symbolFolder { get; init; } 11 | string stopDistanceInPips { get; set; } 12 | string limitDistanceInPips { get; set; } 13 | string elasticPassword { get; init; } 14 | string elasticUser { get; init; } 15 | string elasticCloudID { get; init; } 16 | string accountEquity { get; init; } 17 | string maximumDrawndownPercentage { get; init; } 18 | string s3Bucket { get; init; } 19 | string s3Path { get; init; } 20 | string hostname { get; init; } 21 | string runIteration { get; init; } 22 | string scalingFactor { get; set; } 23 | string tickDataFolder { get; init; } 24 | string tradingSize { get; set; } 25 | int instanceCount { get; init;} 26 | bool reportingEnabled { get; init; } 27 | string[] symbols { get; init; } 28 | int[] years { get; init; } 29 | int yearsStart {get; init;} 30 | int yearsEnd { get; init; } 31 | int kineticStopLoss {get; init;} 32 | int kineticLimit {get; init;} 33 | bool doNotCleanUpDataFolder {get;init;} 34 | bool fasterProcessingBySkippingSomeTickData {get;init;} 35 | 36 | decimal? variableA {get;set;} 37 | decimal? variableB {get;set;} 38 | decimal? variableC {get;set;} 39 | decimal? variableD {get;set;} 40 | decimal? variableE {get;set;} 41 | 42 | Dictionary getScalingFactorDictionary(); 43 | decimal GetScalingFactor(string symbol); 44 | 45 | } -------------------------------------------------------------------------------- /src/Models/OHLCObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace backtesting_engine; 5 | 6 | public class OhlcObject { 7 | [JsonProperty("d")] 8 | public DateTime date {get;set;} = DateTime.MinValue; 9 | [JsonProperty("o")] 10 | public decimal open {get;set;} = Decimal.Zero; 11 | [JsonProperty("c")] 12 | public decimal close {get;set;} = Decimal.Zero; 13 | [JsonProperty("h")] 14 | public decimal high {get;set;} = Decimal.MinValue; 15 | [JsonProperty("l")] 16 | public decimal low {get;set;} = Decimal.MaxValue; 17 | [JsonIgnore] 18 | public bool complete {get;set;} = false; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/Models/PriceObject.cs: -------------------------------------------------------------------------------- 1 | namespace backtesting_engine; 2 | 3 | public class PriceObj { 4 | public string symbol { get;set; } = ""; 5 | public decimal bid {get;set;} 6 | public decimal ask {get;set;} 7 | public DateTime date {get; set;} 8 | } 9 | -------------------------------------------------------------------------------- /src/Models/RequetObject.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine; 2 | using backtesting_engine.interfaces; 3 | 4 | namespace backtesting_engine_models; 5 | 6 | public class RequestObject { 7 | 8 | private readonly IEnvironmentVariables env; 9 | 10 | public RequestObject(PriceObj priceObj, TradeDirection direction, IEnvironmentVariables env, string key) 11 | { 12 | this.priceObj = priceObj; 13 | this.direction = direction; 14 | this.openDate = priceObj.date; 15 | this.key = key; 16 | this.symbol = priceObj.symbol; 17 | this.env = env; 18 | this.scalingFactor = env.GetScalingFactor(priceObj.symbol); 19 | } 20 | 21 | // When setting the direction, set the current level (ASK/BID) 22 | private TradeDirection _direction; 23 | public TradeDirection direction { 24 | get { 25 | return _direction; 26 | } 27 | set { 28 | _direction = value; 29 | this.level = _direction == TradeDirection.BUY ? this.priceObj.ask : this.priceObj.bid; 30 | } 31 | } 32 | 33 | private readonly decimal scalingFactor; 34 | 35 | private decimal _stopDistancePips; 36 | public decimal stopDistancePips { 37 | get{ 38 | return _stopDistancePips; 39 | } 40 | set { 41 | this._stopDistancePips = value; 42 | 43 | if (this.direction == TradeDirection.SELL) 44 | { 45 | this.stopLevel = this.level + (this._stopDistancePips / env.GetScalingFactor(this.symbol)); 46 | 47 | } else if (this.direction == TradeDirection.BUY) 48 | { 49 | this.stopLevel = this.level - (this._stopDistancePips / env.GetScalingFactor(this.symbol)); 50 | } 51 | } 52 | } 53 | 54 | private decimal _limitDistancePips; 55 | public decimal limitDistancePips { 56 | get { 57 | return this._limitDistancePips; 58 | } 59 | set { 60 | this._limitDistancePips = value; 61 | 62 | if (this.direction == TradeDirection.SELL) 63 | { 64 | this.limitLevel = this.level - (this.limitDistancePips / env.GetScalingFactor(this.symbol)); 65 | 66 | } 67 | else if (this.direction == TradeDirection.BUY) 68 | { 69 | this.limitLevel = this.level + (this.limitDistancePips / env.GetScalingFactor(this.symbol)); 70 | } 71 | } 72 | } 73 | 74 | public decimal stopLevel { get; set; } 75 | public decimal limitLevel { get; set; } 76 | 77 | // Read only properties, defined in the constructor 78 | public string key {get;} 79 | public PriceObj priceObj { get; } 80 | public DateTime openDate {get;} 81 | public string symbol {get;} 82 | 83 | // Private set propertises 84 | public decimal level {get; private set;} 85 | public decimal closeLevel {get; private set;} 86 | public decimal profit {get; private set;} 87 | public DateTime closeDateTime {get; private set;} 88 | 89 | // Can only be set in the initialisation of the object 90 | public decimal size {get;set;} 91 | 92 | 93 | public void UpdateClose(PriceObj priceObj){ 94 | 95 | this.closeLevel = this.direction == TradeDirection.BUY ? priceObj.bid : priceObj.ask; 96 | this.closeDateTime = priceObj.date; 97 | this.profit = ((this.direction == TradeDirection.BUY ? this.closeLevel - this.level : this.level - this.closeLevel) 98 | * this.scalingFactor) 99 | * this.size; 100 | 101 | } 102 | 103 | // public void UpdateLevelWithSlippage(decimal slippage){ 104 | // this.level = this.direction == TradeDirection.BUY ? this.level-slippage : this.level+slippage; 105 | // } 106 | } 107 | -------------------------------------------------------------------------------- /src/Models/TradeDirection.cs: -------------------------------------------------------------------------------- 1 | namespace backtesting_engine_models; 2 | 3 | public enum TradeDirection { 4 | BUY, 5 | SELL 6 | } 7 | 8 | public enum TradeRun { 9 | COMPLETE, 10 | INCOMPLETE 11 | } -------------------------------------------------------------------------------- /src/Models/TradeHistoryObject.cs: -------------------------------------------------------------------------------- 1 | namespace backtesting_engine_models; 2 | 3 | public class TradeHistoryObject { 4 | public string key {get;set;} = ""; 5 | public string symbol {get;set;} = ""; 6 | public TradeDirection direction { get;set;} 7 | public decimal size {get;set;} 8 | public decimal profit {get;set;} 9 | public decimal level {get;set;} 10 | public decimal scalingFactor {get;set;} 11 | public decimal closeLevel {get;set;} 12 | public DateTime openDate {get;set;} 13 | public DateTime closeDateTime {get;set;} 14 | public double runningTime {get;set;} 15 | public decimal stopLevel {get;set;} 16 | public decimal limitLevel {get;set;} 17 | public RequestObject? reqObj {get;set;} 18 | } 19 | -------------------------------------------------------------------------------- /src/Models/VolatilityObject.cs: -------------------------------------------------------------------------------- 1 | namespace backtesting_engine_models; 2 | 3 | public class VolatilityObject { 4 | public DateTime date {get;set;} 5 | public string[] symbols {get;set;} = Array.Empty(); 6 | public string runID {get;set;} = string.Empty; 7 | public int runIteration {get;set;} 8 | public string strategy {get;set;} = string.Empty; 9 | public decimal dayClose { get;set;} 10 | public decimal dayRange { get;set;} 11 | public decimal totalMovement { get;set;} 12 | public decimal distanceBetweenPriceMoves { get;set;} 13 | public decimal dayCloseGap { get;set;} 14 | public decimal spreadDistance {get;set;} 15 | } -------------------------------------------------------------------------------- /src/Models/models.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/backtesting/Abstracts/TradingBase.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine.interfaces; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace backtesting_engine; 5 | 6 | public abstract class TradingBase { 7 | 8 | protected ITradingObjects tradingObjects; 9 | protected ISystemObjects systemObjects; 10 | 11 | protected TradingBase(IServiceProvider provider) 12 | { 13 | this.tradingObjects = provider.GetRequiredService(); 14 | this.systemObjects = provider.GetRequiredService(); 15 | } 16 | } -------------------------------------------------------------------------------- /src/backtesting/Consumer/Consumer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks.Dataflow; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_operations; 5 | using backtesting_engine_strategies; 6 | using backtesting_engine_web; 7 | 8 | namespace backtesting_engine_ingest; 9 | 10 | public class Consumer : IConsumer 11 | { 12 | readonly IEnumerable? strategies; 13 | readonly IPositions positions; 14 | 15 | public Consumer(IEnumerable strategies, IPositions positions) { 16 | 17 | this.strategies = strategies; 18 | this.positions = positions; 19 | } 20 | 21 | public async Task ConsumeAsync(BufferBlock buffer, CancellationToken cts) 22 | { 23 | while (await buffer.OutputAvailableAsync()) 24 | { 25 | // Cancel this task if a cancellation token is received 26 | cts.ThrowIfCancellationRequested(); 27 | 28 | // Get the symbol data off the buffer 29 | var priceObj = await buffer.ReceiveAsync(); 30 | 31 | await ProcessTick(priceObj); 32 | } 33 | } 34 | 35 | private async Task ProcessTick(PriceObj priceObj){ 36 | 37 | // Invoke all the strategies defined in configuration 38 | foreach (var i in strategies ?? Array.Empty()) 39 | { 40 | await i.Invoke(priceObj); 41 | } 42 | 43 | // Review open positions, check if the new symbol data meets the threshold for LIMI/STOP levels 44 | await this.positions.Review(priceObj); 45 | this.positions.TrailingStopLoss(priceObj); 46 | this.positions.ReviewEquity(); 47 | this.positions.PushRequests(priceObj); 48 | } 49 | 50 | } 51 | 52 | public static class PropertyCopier where TParent : class 53 | where TChild : class 54 | { 55 | public static void Copy(TParent parent, TChild child) 56 | { 57 | var parentProperties = parent.GetType().GetProperties(); 58 | var childProperties = child.GetType().GetProperties(); 59 | foreach (var parentProperty in parentProperties) 60 | { 61 | foreach (var childProperty in childProperties) 62 | { 63 | if (parentProperty.Name == childProperty.Name && parentProperty.PropertyType == childProperty.PropertyType) 64 | { 65 | childProperty.SetValue(child, parentProperty.GetValue(parent)); 66 | break; 67 | } 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/backtesting/Consumer/WebConsumer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks.Dataflow; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_operations; 5 | using backtesting_engine_strategies; 6 | using backtesting_engine_web; 7 | 8 | namespace backtesting_engine_ingest; 9 | 10 | public class WebConsumer : IConsumer 11 | { 12 | readonly IEnumerable? strategies; 13 | readonly IPositions positions; 14 | readonly IWebUtils webUtils; 15 | 16 | public WebConsumer(IEnumerable strategies, IPositions positions, IWebUtils webUtils) { 17 | 18 | this.strategies = strategies; 19 | this.positions = positions; 20 | this.webUtils = webUtils; 21 | } 22 | 23 | private DateTime lastReceived = DateTime.Now; 24 | private DateTime priceTimeWindow = DateTime.MinValue; 25 | 26 | public async Task ConsumeAsync(BufferBlock buffer, CancellationToken cts) 27 | { 28 | while (await buffer.OutputAvailableAsync()) 29 | { 30 | // Cancel this task if a cancellation token is received 31 | cts.ThrowIfCancellationRequested(); 32 | 33 | // Get the symbol data off the buffer 34 | var priceObj = await buffer.ReceiveAsync(); 35 | 36 | 37 | // take in 1 hour (priceObj) over 100 millisecionds (local) 38 | if(priceTimeWindow==DateTime.MinValue){ 39 | priceTimeWindow=priceObj.date; 40 | } 41 | 42 | await CacheRequests(priceObj); 43 | } 44 | } 45 | 46 | private async Task CacheRequests(PriceObj priceObj){ 47 | // Clock the local time, check it's under 100 milliseconds 48 | if(DateTime.Now.Subtract(lastReceived).TotalSeconds <= 0.2){ //0.4 49 | 50 | // Have we sent an hour 51 | if(priceObj.date.Subtract(priceTimeWindow).TotalMinutes<5){ 52 | await ProcessTick(priceObj); 53 | } else { 54 | await Task.Delay(100); 55 | await CacheRequests(priceObj); 56 | } 57 | 58 | } else { 59 | lastReceived=DateTime.Now; 60 | priceTimeWindow=priceObj.date; 61 | await CacheRequests(priceObj); 62 | } 63 | } 64 | 65 | private async Task ProcessTick(PriceObj priceObj){ 66 | 67 | // Invoke all the strategies defined in configuration 68 | foreach (var i in strategies ?? Array.Empty()) 69 | { 70 | await i.Invoke(priceObj); 71 | } 72 | 73 | // Review open positions, check if the new symbol data meets the threshold for LIMI/STOP levels 74 | await this.positions.Review(priceObj); 75 | this.positions.TrailingStopLoss(priceObj); 76 | this.positions.ReviewEquity(); 77 | this.positions.PushRequests(priceObj); 78 | await this.webUtils.Invoke(priceObj); 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /src/backtesting/Exceptions/TradingException.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Runtime.Serialization; 3 | using System.Security.Permissions; 4 | using backtesting_engine.interfaces; 5 | using Newtonsoft.Json; 6 | using Utilities; 7 | 8 | namespace trading_exception; 9 | 10 | [SuppressMessage("Sonar Code Smell", "S3925:ISerializable should be implemented correctly", Justification = "Extending the exception class to add a date reference, for use in elastic")] 11 | public class TradingException : Exception { 12 | 13 | public DateTime date {get;set;} 14 | public string hostname {get;set;} 15 | public string symbols {get;set;} 16 | public string runID {get;set;} 17 | public string runIteration {get;set;} 18 | public string stacktrace {get;set;} = string.Empty; 19 | 20 | public TradingException(string? message, string? stacktrace, IEnvironmentVariables envVariables) : base(message) { 21 | date = DateTime.Now; 22 | hostname = envVariables.hostname; 23 | symbols = JsonConvert.SerializeObject(envVariables.symbols); 24 | runID = envVariables.runID; 25 | runIteration = envVariables.runIteration; 26 | stacktrace= stacktrace ?? "empty"; 27 | } 28 | 29 | public TradingException(string? message, Exception? innerException, IEnvironmentVariables envVariables) : base(message, innerException) 30 | { 31 | date = DateTime.Now; 32 | hostname = envVariables.hostname; 33 | symbols = JsonConvert.SerializeObject(envVariables.symbols); 34 | runID = envVariables.runID; 35 | runIteration = envVariables.runIteration; 36 | stacktrace= stacktrace ?? "empty"; 37 | } 38 | } -------------------------------------------------------------------------------- /src/backtesting/Objects/InitialReport.cs: -------------------------------------------------------------------------------- 1 | namespace backtesting_engine; 2 | 3 | class InitialReport { 4 | public string hostname {get;set;} = string.Empty; 5 | public DateTime date {get;set;} 6 | public string[] symbols {get;set;} = Array.Empty(); 7 | public string runID {get;set;} = string.Empty; 8 | public int runIteration {get;set;} 9 | public int instanceCount {get;set;} = 0; 10 | public string strategy {get;set;} = string.Empty; 11 | } 12 | 13 | class YearUpdate : InitialReport { 14 | public int year {get;set;} = 0; 15 | } -------------------------------------------------------------------------------- /src/backtesting/Objects/StrategyObjects.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | using backtesting_engine; 4 | using backtesting_engine.interfaces; 5 | using Utilities; 6 | 7 | namespace backtesting_engine; 8 | 9 | public class StrategyObjects : IStrategyObjects 10 | { 11 | private DateTime lastTraded = DateTime.MinValue; 12 | 13 | public async Task> GetOHLCObject(PriceObj priceObj, decimal price, TimeSpan duration, List OHLCArray) 14 | { 15 | return await Task.FromResult(GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, duration, OHLCArray)); 16 | } 17 | 18 | public async Task GetLastTraded(PriceObj priceObj) 19 | { 20 | return await Task.FromResult(lastTraded); 21 | } 22 | 23 | public async Task SetLastTraded(PriceObj priceObj) 24 | { 25 | lastTraded=priceObj.date; 26 | await Task.CompletedTask; 27 | } 28 | } -------------------------------------------------------------------------------- /src/backtesting/Objects/SystemObjects.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine.interfaces; 2 | 3 | namespace backtesting_engine; 4 | 5 | public class SystemObjects : ISystemObjects 6 | { 7 | public DateTime systemStartTime { get; } = DateTime.Now; 8 | public string systemMessage { get; set; } = string.Empty; 9 | } -------------------------------------------------------------------------------- /src/backtesting/Objects/TradingObjects.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine; 8 | 9 | public class TradingObjects : ITradingObjects 10 | { 11 | public TradingObjects(IEnvironmentVariables envVariables) { 12 | accountObj = new AccountObj(openTrades, 13 | tradeHistory, 14 | decimal.Parse(envVariables.accountEquity), 15 | decimal.Parse(envVariables.maximumDrawndownPercentage), 16 | envVariables); 17 | } 18 | 19 | public ConcurrentDictionary openTrades { get; } = new ConcurrentDictionary(); 20 | public ConcurrentDictionary tradeHistory { get; } = new ConcurrentDictionary(); 21 | public string test { get; set; } = ""; 22 | public DateTime tradeTime { get; set; } 23 | public AccountObj accountObj {get; init;} 24 | // public interfaces.IAccountObj accountObj { get; init; } 25 | } -------------------------------------------------------------------------------- /src/backtesting/Operations/BacktestingOpenTrades.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine; 2 | using backtesting_engine.interfaces; 3 | using backtesting_engine_models; 4 | using Utilities; 5 | 6 | namespace backtesting_engine_operations; 7 | 8 | public class BacktestingOpenTrades : IOpenTrades 9 | { 10 | 11 | protected readonly ITradingObjects tradeObjs; 12 | 13 | public BacktestingOpenTrades(ITradingObjects tradeObjs) 14 | { 15 | this.tradeObjs = tradeObjs; 16 | } 17 | 18 | public Task Request(string symbol) 19 | { 20 | return Task.FromResult(tradeObjs.openTrades.Count); 21 | } 22 | } -------------------------------------------------------------------------------- /src/backtesting/Operations/RequestOpenTrade.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine; 2 | using backtesting_engine.interfaces; 3 | using backtesting_engine_models; 4 | using Utilities; 5 | 6 | namespace backtesting_engine_operations; 7 | 8 | public class RequestOpenTrade : IRequestOpenTrade 9 | { 10 | 11 | readonly IOpenOrder openOrder; 12 | 13 | public RequestOpenTrade(IOpenOrder openOrder) 14 | { 15 | this.openOrder = openOrder; 16 | } 17 | 18 | public Task Request(RequestObject reqObj) 19 | { 20 | 21 | // Open trade validation 22 | // If hours are between 23 | // If X amount of trades are open 24 | 25 | this.openOrder.Request(reqObj); 26 | 27 | return Task.FromResult(true); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/backtesting/Program.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine_ingest; 2 | using Utilities; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using backtesting_engine_operations; 5 | using backtesting_engine.interfaces; 6 | using Nest; 7 | using Elasticsearch.Net; 8 | using backtesting_engine.analysis; 9 | using System.Net; 10 | using backtesting_engine_web; 11 | using System.Diagnostics; 12 | 13 | namespace backtesting_engine; 14 | 15 | public static class Program 16 | { 17 | private static EnvironmentVariables variables = new EnvironmentVariables(); 18 | static CloudConnectionPool pool = new CloudConnectionPool(variables.elasticCloudID, new BasicAuthenticationCredentials(variables.elasticUser, variables.elasticPassword)); 19 | static ConnectionSettings settings = new ConnectionSettings(pool).RequestTimeout(TimeSpan.FromMinutes(2)).EnableApiVersioningHeader(); 20 | 21 | public async static Task Main(string[] args) { 22 | 23 | IServiceCollection? serviceCollection=null; 24 | 25 | if(serviceCollection == null){ 26 | serviceCollection = new ServiceCollection(); 27 | } 28 | 29 | if(args.Count() > 0 && args[0] == "web"){ 30 | System.Console.WriteLine("Web"); 31 | 32 | _ = Task.Run(() => { 33 | Webserver.Api.Program.Main(new string[1]{"web"}); 34 | }).ContinueWith(task => { 35 | if(task.IsFaulted){ 36 | Console.WriteLine(task.Exception?.Message); 37 | Console.WriteLine(task.Exception?.InnerException); 38 | 39 | } 40 | if(task.IsCanceled){ 41 | System.Console.WriteLine("Task cancelled"); 42 | } 43 | if(task.IsCompletedSuccessfully){ 44 | Console.WriteLine("Task completed successfully"); 45 | } 46 | System.Console.WriteLine("Web Server closed"); 47 | Environment.Exit(0); 48 | } 49 | ); 50 | 51 | Thread.Sleep(2000); // wait for web server to boot up 52 | serviceCollection.AddSingleton(); 53 | serviceCollection.AddSingleton(); 54 | serviceCollection.AddTransient(); 55 | } else { 56 | System.Console.WriteLine("Not web"); 57 | serviceCollection.AddSingleton(); 58 | serviceCollection.AddSingleton(); 59 | serviceCollection.AddTransient(); 60 | } 61 | 62 | serviceCollection 63 | .RegisterStrategies(variables) 64 | .AddSingleton( (IServiceProvider provider) => { 65 | var esClient = new ElasticClient(settings); 66 | //Send an initial report to ElasticSearch 67 | esClient.Index(new InitialReport() { 68 | hostname = Dns.GetHostName(), 69 | date = DateTime.Now, 70 | symbols = variables.symbols, 71 | runID = variables.runID, 72 | runIteration = int.Parse(variables.runIteration), 73 | strategy = variables.strategy, 74 | instanceCount = variables.instanceCount 75 | }, b => b.Index("init")); 76 | return esClient; 77 | }) 78 | .AddTransient() 79 | .AddSingleton() 80 | .AddTransient() 81 | .AddTransient() 82 | .AddTransient() 83 | .AddTransient() 84 | .AddSingleton() 85 | .AddSingleton() 86 | .AddTransient() 87 | .AddSingleton() 88 | .AddSingleton() 89 | .AddSingleton() 90 | .AddSingleton(variables); 91 | 92 | Stopwatch sw = new Stopwatch(); 93 | sw.Start(); 94 | 95 | await Task.FromResult( 96 | serviceCollection 97 | .BuildServiceProvider(true) 98 | .CreateScope() 99 | .ServiceProvider.GetRequiredService()) 100 | .ContinueWith(task => { 101 | if(task.IsFaulted){ 102 | Console.WriteLine(task.Exception?.Message); 103 | Console.WriteLine(task.Exception?.StackTrace); 104 | } 105 | if(task.IsCanceled){ 106 | System.Console.WriteLine("Task cancelled"); 107 | } 108 | if(task.IsCompletedSuccessfully){ 109 | Console.WriteLine("Task completed successfully"); 110 | } 111 | 112 | sw.Stop(); 113 | 114 | Console.WriteLine("Elapsed={0}",sw.Elapsed); 115 | } 116 | ); 117 | } 118 | } 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/backtesting/TaskManager.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks.Dataflow; 2 | using backtesting_engine.interfaces; 3 | using backtesting_engine_ingest; 4 | using trading_exception; 5 | 6 | namespace backtesting_engine; 7 | 8 | public class TaskManager : ITaskManager 9 | { 10 | public BufferBlock buffer { get; init; } 11 | protected readonly CancellationTokenSource cts = new CancellationTokenSource(); 12 | 13 | readonly IConsumer con; 14 | readonly IIngest ing; 15 | readonly IEnvironmentVariables envVaribles; 16 | 17 | public TaskManager(IConsumer c, IIngest i, IEnvironmentVariables env) 18 | { 19 | this.con = c; 20 | this.ing = i; 21 | this.envVaribles = env; 22 | this.buffer = new BufferBlock(); 23 | } 24 | 25 | public async Task IngestAndConsume() 26 | { 27 | ing.EnvironmentSetup(); 28 | 29 | // Updated to ensure that a stack trace is pushed up the stack 30 | // if either task fails 31 | 32 | Task taskProduce = Task.Run(() => ing.ReadLines(buffer, cts.Token)) 33 | .CancelOnFaulted(cts) 34 | .ContinueWith(task => { 35 | if(task.IsFaulted){ 36 | cts.Cancel(); 37 | buffer.SendAsync(new PriceObj()); 38 | 39 | 40 | if(task.Exception?.InnerException is not TradingException) { 41 | throw new Exception (task.Exception?.Message, task.Exception); 42 | } 43 | } 44 | }); 45 | 46 | Task consumer = Task.Run(() => con.ConsumeAsync(buffer, cts.Token)).CancelOnFaulted(cts) 47 | .ContinueWith(task => { 48 | if(task.IsFaulted){ 49 | cts.Cancel(); 50 | 51 | if(task.Exception?.InnerException is TradingException) { 52 | throw new TradingException(task.Exception?.InnerException.Message, "", envVaribles); 53 | } else { 54 | throw new Exception (task.Exception?.Message, task.Exception); 55 | } 56 | 57 | } 58 | }); 59 | 60 | await Task.WhenAll(taskProduce, consumer); 61 | } 62 | } 63 | 64 | public static class ExtensionMethods 65 | { 66 | public static Task CancelOnFaulted(this Task task, CancellationTokenSource cts) 67 | { 68 | task.ContinueWith(task => cts.Cancel(), cts.Token, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); 69 | return task; 70 | } 71 | } -------------------------------------------------------------------------------- /src/backtesting/TradeManagement/CloseOrder.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine.analysis; 2 | using backtesting_engine.interfaces; 3 | using backtesting_engine_ingest; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine; 8 | 9 | 10 | public class CloseOrder : TradingBase, ICloseOrder 11 | { 12 | 13 | readonly IReporting reporting; 14 | readonly IWebNotification webNotification; 15 | public Dictionary cache; 16 | 17 | 18 | public CloseOrder(IServiceProvider provider, IReporting reporting, IWebNotification webNotification) : base(provider) { 19 | this.reporting = reporting; 20 | cache = new Dictionary(); 21 | this.webNotification=webNotification; 22 | } 23 | 24 | public void PushRequest(PriceObj priceObj){ 25 | foreach(var item in cache){ 26 | 27 | // TODO 28 | // Make this configurable, trades must be kept open a minimum of 1 second 29 | if(priceObj.date.Subtract(item.Value.closeDateTime).TotalSeconds > 1){ 30 | // Update profit position 31 | // item.Value.profit = item.Value.reqObj.profit; 32 | CloseTrade(item.Value); 33 | cache.Remove(item.Key); 34 | } 35 | } 36 | } 37 | 38 | public void Request(RequestObject reqObj, PriceObj priceObj) 39 | { 40 | RequestHandler(reqObj, priceObj, false); 41 | } 42 | 43 | public void Request(RequestObject reqObj, PriceObj priceObj, bool completed) 44 | { 45 | RequestHandler(reqObj, priceObj, completed); 46 | } 47 | // Data Update 48 | private void RequestHandler(RequestObject reqObj, PriceObj priceObj, bool completed) 49 | { 50 | 51 | TradeHistoryObject tradeHistoryObj = new TradeHistoryObject(); 52 | tradeHistoryObj.closeLevel = reqObj.closeLevel; 53 | tradeHistoryObj.profit = reqObj.profit; 54 | tradeHistoryObj.closeDateTime = reqObj.closeDateTime; 55 | tradeHistoryObj.runningTime = reqObj.closeDateTime.Subtract(reqObj.openDate).TotalMinutes; 56 | tradeHistoryObj.key = reqObj.key; 57 | tradeHistoryObj.reqObj = reqObj; 58 | 59 | PropertyCopier.Copy(reqObj, tradeHistoryObj); 60 | 61 | if(completed) { 62 | // Immediately close the trade if we are at the end 63 | CloseTrade(tradeHistoryObj); 64 | } else if(!cache.ContainsKey(tradeHistoryObj.key)){ 65 | cache.Add(tradeHistoryObj.key, tradeHistoryObj); 66 | } 67 | } 68 | 69 | private void CloseTrade(TradeHistoryObject tradeHistoryObj){ 70 | 71 | var key = DictionaryKeyStrings.CloseTradeKey(tradeHistoryObj.symbol, tradeHistoryObj.openDate, tradeHistoryObj.level); 72 | this.tradingObjects.tradeHistory.TryAdd(key, tradeHistoryObj); 73 | this.tradingObjects.accountObj.AddTradeProftOrLoss(tradeHistoryObj.profit); 74 | this.tradingObjects.openTrades.TryRemove(tradeHistoryObj.key, out _); 75 | 76 | ConsoleLogger.Log(tradeHistoryObj.closeDateTime + "\t" + this.tradingObjects.accountObj.pnl.ToString("0.00") + "\t Closed trade for " + tradeHistoryObj.symbol + "\t" + tradeHistoryObj.profit.ToString("0.00") + "\t" + tradeHistoryObj.direction + "\t" + tradeHistoryObj.level.ToString("0.#####") + "\t" + tradeHistoryObj.closeLevel.ToString("0.#####") + "\t" + tradeHistoryObj.runningTime.ToString("0.00")); 77 | 78 | webNotification.TradeUpdate(tradeHistoryObj); 79 | this.reporting.TradeUpdate(tradeHistoryObj.closeDateTime, tradeHistoryObj.symbol, tradeHistoryObj.profit); 80 | } 81 | } -------------------------------------------------------------------------------- /src/backtesting/TradeManagement/OpenOrder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using backtesting_engine_operations; 6 | using Newtonsoft.Json; 7 | using Utilities; 8 | 9 | namespace backtesting_engine; 10 | 11 | public class OpenOrder : TradingBase, IOpenOrder 12 | { 13 | readonly IWebNotification webNotification; 14 | 15 | public OpenOrder(IServiceProvider provider, IWebNotification webNotification) : base(provider) { 16 | this.webNotification = webNotification; 17 | 18 | } 19 | 20 | // Data Update 21 | public void Request(RequestObject reqObj) 22 | { 23 | 24 | // Moved trading restrictions (eg. max open trades) into the strategy 25 | tradingObjects.openTrades.TryAdd(reqObj.key, reqObj); 26 | webNotification.OpenTrades(reqObj); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/backtesting/Utilities/ParseDecimal.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace Utilities; 4 | 5 | public static class DecimalUtil 6 | { 7 | 8 | private static int[] powof10 = new int[10] 9 | { 10 | 1, 11 | 10, 12 | 100, 13 | 1000, 14 | 10000, 15 | 100000, 16 | 1000000, 17 | 10000000, 18 | 100000000, 19 | 1000000000 20 | }; 21 | 22 | [SuppressMessage("Sonar Code Smell", "S3776:Cognitive Complexity of methods should not be too high", Justification = "Decimal parser taken from open source for faster parsing")] 23 | public static decimal ParseDecimal(string input) 24 | { 25 | int len = input.Length; 26 | if (len != 0) 27 | { 28 | bool negative = false; 29 | long n = 0; 30 | int start = 0; 31 | if (input[0] == '-') 32 | { 33 | negative = true; 34 | start = 1; 35 | } 36 | if (len <= 19) 37 | { 38 | int decpos = len; 39 | for (int k = start; k < len; k++) 40 | { 41 | char c = input[k]; 42 | if (c == '.') 43 | { 44 | decpos = k +1; 45 | }else{ 46 | n = (n *10) +(int)(c -'0'); 47 | } 48 | } 49 | return new decimal((int)n, (int)(n >> 32), 0, negative, (byte)(len -decpos)); 50 | }else{ 51 | if (len > 28) 52 | { 53 | len = 28; 54 | } 55 | int decpos = len; 56 | for (int k = start; k < 19; k++) 57 | { 58 | char c = input[k]; 59 | if (c == '.') 60 | { 61 | decpos = k +1; 62 | }else{ 63 | n = (n *10) +(int)(c -'0'); 64 | } 65 | } 66 | int n2 = 0; 67 | bool secondhalfdec = false; 68 | for (int k = 19; k < len; k++) 69 | { 70 | char c = input[k]; 71 | if (c == '.') 72 | { 73 | decpos = k +1; 74 | secondhalfdec = true; 75 | }else{ 76 | n2 = (n2 *10) +(int)(c -'0'); 77 | } 78 | } 79 | byte decimalPosition = (byte)(len -decpos); 80 | return new decimal((int)n, (int)(n >> 32), 0, negative, decimalPosition) *powof10[len -(!secondhalfdec ? 19 : 20)] +new decimal(n2, 0, 0, negative, decimalPosition); 81 | } 82 | } 83 | return 0; 84 | } 85 | } -------------------------------------------------------------------------------- /src/backtesting/Utilities/ReportObj.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine.interfaces; 2 | using Utilities; 3 | 4 | namespace backtesting_engine; 5 | 6 | public class ReportFinalObj { 7 | 8 | // System information 9 | public DateTime date {get;set;} 10 | public string? runID {get;set;} 11 | public string? hostname {get;set;} 12 | public double systemRunTimeInMinutes {get;set;} 13 | public int runIteration {get;set;} 14 | public string? systemMessage {get;set;} 15 | 16 | // Engine Information 17 | public string[]? symbols { get;set; } 18 | public string? strategy {get;set;} 19 | public bool complete {get;set;} = true; 20 | public string? reason {get;set;} 21 | public string? detailedReason {get;set;} 22 | 23 | // Account stats 24 | public decimal openingEquity { get;set; } 25 | public decimal maximumDrawndownPercentage {get;set;} 26 | 27 | // Trading Stats 28 | public decimal pnl {get;set;} 29 | public decimal totalProfit {get;set;} 30 | public decimal totalLoss {get;set;} 31 | public double tradingTimespanInDays {get;set;} 32 | public int positiveTradeCount {get;set;} 33 | public int negativeTradeCount {get;set;} 34 | public decimal positivePercentage {get;set;} 35 | public bool yearOnYearReturn {get;set;} 36 | 37 | public decimal stopDistanceInPips {get;set;} 38 | public decimal limitDistanceInPips {get;set;} 39 | 40 | public int instanceCount { get;set; } 41 | 42 | public decimal? variableA {get;set;} 43 | public decimal? variableB {get;set;} 44 | public decimal? variableC {get;set;} 45 | public decimal? variableD {get;set;} 46 | public decimal? variableE {get;set;} 47 | 48 | public IEnvironmentVariables? environmentVariables {get;set;} 49 | } 50 | 51 | public class ReportTradeObj { 52 | public string id {get;} = Guid.NewGuid().ToString(); 53 | public DateTime date {get;set;} 54 | public string[]? symbols { get;set; } 55 | public decimal pnl {get;set;} 56 | public string? runID {get;set;} 57 | public decimal tradeProfit {get;set;} 58 | public int runIteration {get;set;} 59 | public decimal stopDistanceInPips {get;set;} 60 | public decimal limitDistanceInPips {get;set;} 61 | public decimal trailingStopLoss {get;set;} 62 | public int instanceCount { get; init; } = 0; 63 | public IEnvironmentVariables? environmentVariables {get;set;} 64 | 65 | public decimal? variableA {get;set;} 66 | public decimal? variableB {get;set;} 67 | public decimal? variableC {get;set;} 68 | public decimal? variableD {get;set;} 69 | public decimal? variableE {get;set;} 70 | } 71 | -------------------------------------------------------------------------------- /src/backtesting/Utilities/ShellHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Diagnostics.CodeAnalysis; 3 | using Microsoft.Extensions.Logging; 4 | using Newtonsoft.Json; 5 | 6 | namespace Utilities; 7 | 8 | public static class ShellHelper 9 | { 10 | 11 | public static string RunCommandWithBash(this string command) 12 | { 13 | var escapedArgs = command.Replace("\"", "\\\""); 14 | var psi = new ProcessStartInfo(); 15 | psi.FileName = "/bin/bash"; 16 | psi.Arguments = $"-c \"{escapedArgs}\""; 17 | psi.RedirectStandardOutput = true; 18 | psi.UseShellExecute = false; 19 | psi.CreateNoWindow = true; 20 | 21 | using var process = Process.Start(psi); 22 | 23 | process?.WaitForExit(); 24 | return process?.StandardOutput.ReadToEnd() ?? ""; 25 | } 26 | 27 | public static Task Bash(this string cmd) 28 | { 29 | var source = new TaskCompletionSource(); 30 | var escapedArgs = cmd.Replace("\"", "\\\""); 31 | var process = new Process { 32 | StartInfo = new ProcessStartInfo 33 | { 34 | FileName = "/bin/bash", 35 | Arguments = $"-c \"{escapedArgs}\"", 36 | RedirectStandardOutput = true, 37 | RedirectStandardError = true, 38 | UseShellExecute = false 39 | }, 40 | EnableRaisingEvents = true 41 | }; 42 | 43 | process.OutputDataReceived += (object sender, DataReceivedEventArgs e) =>{ 44 | ConsoleLogger.SystemLog(e.Data ?? ""); 45 | }; 46 | 47 | process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) =>{ 48 | if(!string.IsNullOrEmpty(e.Data)){ 49 | ConsoleLogger.SystemLog(e.Data); 50 | } 51 | }; 52 | 53 | process.Exited += (sender, args) => 54 | { 55 | if (process.ExitCode == 0) { 56 | source.SetResult(0); 57 | } else { 58 | source.SetException(new Exception($"Command `{cmd}` failed with exit code `{process.ExitCode}`")); 59 | } 60 | process.Dispose(); 61 | }; 62 | 63 | try { 64 | process.Start(); 65 | process.BeginOutputReadLine(); 66 | process.BeginErrorReadLine(); 67 | 68 | } catch (Exception e) { 69 | source.SetException(e); 70 | } 71 | return source.Task; 72 | } 73 | } -------------------------------------------------------------------------------- /src/backtesting/Utilities/StringFormats.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities 2 | { 3 | public static class StringFormats 4 | { 5 | public static string dtFormat { get; } = "yyyy-MM-ddTHH:mm:ss.fff"; 6 | public static char[] sep { get; } = ",".ToCharArray(); 7 | } 8 | } -------------------------------------------------------------------------------- /src/backtesting/Web/EmptyNotification.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine.interfaces; 2 | using backtesting_engine_models; 3 | 4 | namespace backtesting_engine; 5 | 6 | 7 | // To be injected when backtesting without the web viewer (headless) 8 | public class EmptyWebNotification : IWebNotification 9 | { 10 | public EmptyWebNotification(){ 11 | } 12 | 13 | public Task AccountUpdate(decimal input) 14 | { 15 | return Task.CompletedTask; 16 | } 17 | 18 | public Task OpenTrades(RequestObject input) 19 | { 20 | return Task.CompletedTask; 21 | } 22 | 23 | public Task PriceUpdate(OhlcObject input, bool force) 24 | { 25 | return Task.CompletedTask; 26 | } 27 | 28 | public Task TradeUpdate(TradeHistoryObject input) 29 | { 30 | return Task.CompletedTask; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/backtesting/Web/WebNotification.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine.interfaces; 2 | using backtesting_engine_models; 3 | using Newtonsoft.Json; 4 | 5 | namespace backtesting_engine; 6 | 7 | public class WebNotification : IWebNotification 8 | { 9 | private DateTime lastSentAccount = DateTime.Now; 10 | private DateTime lastSentOpenTrade = DateTime.Now; 11 | private Dictionary> groupMessage = new Dictionary>(); 12 | 13 | public async Task AccountUpdate(decimal input) 14 | { 15 | 16 | // if(DateTime.Now.Subtract(lastSentAccount).TotalMilliseconds < 500){ 17 | // return; 18 | // } 19 | // lastSentAccount = DateTime.Now; 20 | 21 | // await PublishMessage("account", input.ToString()); 22 | 23 | if(!groupMessage.ContainsKey("accountUpdate")){ 24 | groupMessage["accountUpdate"] = new List(); 25 | groupMessage["accountUpdate"].Add(""); 26 | } 27 | 28 | groupMessage["accountUpdate"][0] = input.ToString(); 29 | 30 | await Task.CompletedTask; 31 | } 32 | 33 | private Dictionary tradeArray=new Dictionary(); 34 | 35 | public async Task OpenTrades(RequestObject input) 36 | { 37 | 38 | // if(DateTime.Now.Subtract(lastSentOpenTrade).TotalMilliseconds < 500){ 39 | // return; 40 | // } 41 | // lastSentOpenTrade = DateTime.Now; 42 | 43 | tradeArray.Add(input.key, input); 44 | 45 | // await PublishMessage("openTrades", JsonConvert.SerializeObject(input)); 46 | 47 | // Surpress CS1998 48 | // Async method lacks 'await' operators and will run synchronously 49 | await Task.CompletedTask; 50 | } 51 | 52 | public async Task TradeUpdate(TradeHistoryObject input) 53 | { 54 | 55 | if(!groupMessage.ContainsKey("trade")){ 56 | groupMessage["trade"] = new List(); 57 | } 58 | groupMessage["trade"].Add(JsonConvert.SerializeObject(input)); 59 | 60 | // await PublishMessage("trade", JsonConvert.SerializeObject(input)); 61 | 62 | // Surpress CS1998 63 | // Async method lacks 'await' operators and will run synchronously 64 | await Task.CompletedTask; 65 | } 66 | 67 | private decimal lastClose = decimal.Zero; 68 | private DateTime lastSent = DateTime.Now; 69 | 70 | public async Task PriceUpdate(OhlcObject input, bool force = false) 71 | { 72 | 73 | if(DateTime.Now.Subtract(lastSent).TotalMilliseconds < 200 && !force){ 74 | return; 75 | } 76 | 77 | lastSent=DateTime.Now; 78 | 79 | if(lastClose == input.close && !force){ 80 | return; 81 | } 82 | lastClose=input.close; 83 | 84 | if(!groupMessage.ContainsKey("priceUpdate")){ 85 | groupMessage["priceUpdate"] = new List(); 86 | groupMessage["priceUpdate"].Add(""); 87 | } 88 | groupMessage["priceUpdate"][0]=JsonConvert.SerializeObject(input); 89 | 90 | 91 | if(!groupMessage.ContainsKey("openTrades")){ 92 | groupMessage["openTrades"] = new List(); 93 | } 94 | 95 | foreach(var item in tradeArray){ 96 | groupMessage["openTrades"].Add(JsonConvert.SerializeObject(item.Value)); 97 | } 98 | 99 | await PublishMessage("price", JsonConvert.SerializeObject(groupMessage)); 100 | 101 | // Clean up 102 | groupMessage = new Dictionary>(); 103 | tradeArray = new Dictionary(); 104 | } 105 | 106 | private async Task PublishMessage(string activity, string content){ 107 | try { 108 | if(Webserver.Api.Program.hubContext!=null){ 109 | await Webserver.Api.Program.hubContext.Clients.All.ReceiveMessage(new Webserver.Api.Models.ChatMessage(){ 110 | Activity=activity, 111 | Content=content 112 | }); 113 | } 114 | } 115 | catch(Exception hubEx){ 116 | System.Console.WriteLine(hubEx); 117 | } 118 | } 119 | 120 | } 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/backtesting/backtesting-engine.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | backtesting_engine 7 | enable 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/live/ingest/src/Function.cs: -------------------------------------------------------------------------------- 1 | using Amazon.DynamoDBv2; 2 | using Amazon.DynamoDBv2.DataModel; 3 | using Amazon.DynamoDBv2.DocumentModel; 4 | using Amazon.Lambda.Core; 5 | using backtesting_engine; 6 | using Newtonsoft.Json; 7 | 8 | // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. 9 | [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] 10 | 11 | namespace ingest; 12 | 13 | public class Function 14 | { 15 | 16 | private static AmazonDynamoDBClient dynamoDBclient = new AmazonDynamoDBClient(); 17 | private static DynamoDBContext dynamoDBContext = new DynamoDBContext(dynamoDBclient); 18 | 19 | public string FunctionHandler(PriceObj input, ILambdaContext context) 20 | { 21 | 22 | var marketObj = new Document(); 23 | marketObj["bid"] = input.bid; 24 | marketObj["ask"] = input.ask; 25 | marketObj["date"] = input.date; 26 | marketObj["symbol"] = input.symbol; 27 | 28 | Table LiveDataTable = Table.LoadTable(dynamoDBclient, "MarketDataLive"); 29 | LiveDataTable.PutItemAsync(marketObj).GetAwaiter().GetResult(); 30 | 31 | return JsonConvert.SerializeObject(input); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/live/ingest/src/aws-lambda-tools-defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "Information": [ 3 | "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", 4 | "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", 5 | "dotnet lambda help", 6 | "All the command line options for the Lambda command can be specified in this file." 7 | ], 8 | "profile": "", 9 | "region": "", 10 | "configuration": "Release", 11 | "function-runtime": "dotnet6", 12 | "function-memory-size": 256, 13 | "function-timeout": 30, 14 | "function-handler": "ingest::ingest.Function::FunctionHandler" 15 | } -------------------------------------------------------------------------------- /src/live/ingest/src/ingest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | enable 5 | enable 6 | true 7 | Lambda 8 | 9 | true 10 | 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/live/ingest/test/FunctionTest.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Amazon.Lambda.Core; 3 | using Amazon.Lambda.TestUtilities; 4 | 5 | namespace ingest.Tests; 6 | 7 | public class FunctionTest 8 | { 9 | // [Fact] 10 | // public void TestToUpperFunction() 11 | // { 12 | 13 | // // Invoke the lambda function and confirm the string was upper cased. 14 | // var function = new Function(); 15 | // var context = new TestLambdaContext(); 16 | // var upperCase = function.FunctionHandler("hello world", context); 17 | 18 | // Assert.Equal("HELLO WORLD", upperCase); 19 | // } 20 | } 21 | -------------------------------------------------------------------------------- /src/live/ingest/test/ingest.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | enable 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/strategies/BaseStrategy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Globalization; 4 | using System.Threading.Tasks.Dataflow; 5 | using backtesting_engine; 6 | using backtesting_engine.interfaces; 7 | using backtesting_engine_models; 8 | using Utilities; 9 | 10 | namespace backtesting_engine_strategies; 11 | 12 | public class BaseStrategy 13 | { 14 | protected readonly IRequestOpenTrade requestOpenTrade; 15 | protected readonly IEnvironmentVariables envVariables; 16 | protected readonly ITradingObjects tradeObjs; 17 | protected readonly IWebNotification webNotification; 18 | protected readonly ICloseOrder closeOrder; 19 | 20 | protected BaseStrategy(IRequestOpenTrade requestOpenTrade, ITradingObjects tradeObjs, IEnvironmentVariables envVariables, ICloseOrder closeOrder, IWebNotification webNotification) 21 | { 22 | this.requestOpenTrade = requestOpenTrade; 23 | this.envVariables = envVariables; 24 | this.tradeObjs = tradeObjs; 25 | this.webNotification = webNotification; 26 | this.closeOrder = closeOrder; 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/strategies/drafts/Momentum.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Newtonsoft.Json; 6 | using Utilities; 7 | 8 | namespace backtesting_engine_strategies; 9 | 10 | // https://mccaffers.com/randomly_trading/ 11 | public class Momentum : BaseStrategy, IStrategy 12 | { 13 | 14 | private List ohlcList = new List(); 15 | private OhlcObject lastItem = new OhlcObject(); 16 | private DateTime lastReportedEvent = DateTime.MinValue; 17 | private PriceObj? earlyMorningPrice; 18 | private PriceObj? lateMorningPrice; 19 | 20 | public Momentum( IRequestOpenTrade requestOpenTrade, 21 | ITradingObjects tradeObjs, 22 | IEnvironmentVariables envVariables, 23 | ICloseOrder closeOrder, 24 | IWebNotification webNotification ) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) {} 25 | 26 | 27 | public async Task Invoke(PriceObj priceObj) 28 | { 29 | 30 | foreach(var item in this.tradeObjs.openTrades.Where(x => x.Key.Contains(priceObj.symbol)).Select(x => x.Value)){ 31 | if(priceObj.date.Subtract(item.openDate).TotalHours > 1 && item.profit > 10){ 32 | this.closeOrder.Request(item, priceObj); 33 | return; 34 | } 35 | 36 | if(priceObj.date.Subtract(item.openDate).TotalHours > 3 && item.profit > 5){ 37 | this.closeOrder.Request(item, priceObj); 38 | return; 39 | } 40 | if(priceObj.date.Subtract(item.openDate).TotalHours > 4 && item.profit > 0){ 41 | this.closeOrder.Request(item, priceObj); 42 | return; 43 | } 44 | } 45 | // Maximum of one open trade 46 | // TODO Make configurable 47 | if (tradeObjs.openTrades.Count() >= 1) 48 | { 49 | return; 50 | } 51 | 52 | if(lastReportedEvent == DateTime.MinValue){ 53 | lastReportedEvent = priceObj.date; 54 | return; 55 | } 56 | 57 | 58 | var randomInt = new Random().Next(8); 59 | 60 | // TradeDirection direction = TradeDirection.BUY; 61 | 62 | if (randomInt==0) 63 | { 64 | return; 65 | } 66 | 67 | // if(priceObj.date.DayOfWeek == DayOfWeek.Sunday) 68 | // { 69 | // return; 70 | // } 71 | 72 | if(priceObj.date.Hour < 6 || priceObj.date.Hour > 23){ 73 | return; 74 | } 75 | 76 | if(priceObj.date.DayOfWeek == DayOfWeek.Sunday || priceObj.date.DayOfWeek == DayOfWeek.Friday ){ 77 | return; 78 | } 79 | 80 | // The day has changed 81 | // if(lastReportedEvent.Hour != priceObj.date.Hour) { 82 | // After 7 am, lets start recording 83 | if(earlyMorningPrice == null){ 84 | earlyMorningPrice=priceObj; 85 | } 86 | 87 | // After 7 am, lets start recording 88 | if(earlyMorningPrice?.date.Subtract(priceObj.date).TotalMinutes < -30 && lateMorningPrice == null) { 89 | lateMorningPrice=priceObj; 90 | } 91 | 92 | if(earlyMorningPrice!=null && lateMorningPrice != null){ 93 | lastReportedEvent=priceObj.date.Date; 94 | var diff = 1 - (earlyMorningPrice.bid / lateMorningPrice.bid); 95 | 96 | if(Math.Abs(diff*1000) >= 1 ){ 97 | 98 | TradeDirection dir = TradeDirection.SELL; 99 | if(diff > 0) { 100 | dir = TradeDirection.BUY; 101 | } 102 | var size = decimal.Parse(envVariables.tradingSize); 103 | 104 | 105 | OpenTrade(priceObj, dir, size); 106 | 107 | 108 | } 109 | 110 | earlyMorningPrice=null; 111 | lateMorningPrice=null; 112 | } 113 | 114 | // } 115 | 116 | 117 | // Surpress CS1998 118 | // Async method lacks 'await' operators and will run synchronously 119 | await Task.CompletedTask; 120 | 121 | return; 122 | } 123 | 124 | private void OpenTrade(PriceObj priceObj, TradeDirection direction, decimal size){ 125 | 126 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 127 | 128 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 129 | { 130 | size = size , 131 | stopDistancePips = decimal.Parse(envVariables.stopDistanceInPips), 132 | limitDistancePips = decimal.Parse(envVariables.limitDistanceInPips), 133 | }; 134 | 135 | this.requestOpenTrade.Request(openOrderRequest); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/strategies/drafts/Momentum_438539dc_many.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine_strategies; 8 | 9 | public class Momentum_438539dc_many : BaseStrategy, IStrategy 10 | { 11 | private List ohlcList = new List(); 12 | 13 | public Momentum_438539dc_many(IRequestOpenTrade requestOpenTrade, IEnvironmentVariables envVariables, ITradingObjects tradeObjs, ICloseOrder closeOrder, IWebNotification webNotification) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) { } 14 | 15 | private OhlcObject lastItem = new OhlcObject(); 16 | 17 | private DateTime lastTraded = DateTime.MinValue; 18 | 19 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 20 | public async Task Invoke(PriceObj priceObj) 21 | { 22 | 23 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromMinutes(30), ohlcList); 24 | 25 | // Maximum of one trade open at a time 26 | // Conditional to only invoke the strategy if there are no trades open 27 | if (tradeObjs.openTrades.Count() >= 1) 28 | { 29 | return; 30 | } 31 | 32 | if(priceObj.date.Subtract(lastTraded).TotalHours < 24){ 33 | return; 34 | } 35 | 36 | 37 | // Keep 5 hours of history (30*10) 38 | // Only start trading once you have 5 hours 39 | if (ohlcList.Count > envVariables.variableA) 40 | { 41 | 42 | lastTraded=priceObj.date; 43 | 44 | // Get the highest value from all of the OHLC objects 45 | var distance = (ohlcList.Last().close - ohlcList.First().close) * envVariables.GetScalingFactor(priceObj.symbol); 46 | var speed = distance / (30 * (envVariables.variableA ?? 0)); 47 | 48 | // System.Console.WriteLine(speed); 49 | 50 | // Too slow 51 | if(Math.Abs(speed) < 1m){ 52 | return; 53 | } 54 | 55 | var direction = TradeDirection.BUY; 56 | if(speed > 0){ 57 | direction = TradeDirection.SELL; 58 | } 59 | 60 | var stopLevel = direction == TradeDirection.BUY ? ohlcList.Min(x => x.low) : 61 | ohlcList.Max(x => x.high); 62 | 63 | var limitLevel = direction == TradeDirection.BUY ? ohlcList.TakeLast(2).Average(x => x.high): 64 | ohlcList.TakeLast(2).Average(x => x.low); 65 | 66 | var stopDistance = direction == TradeDirection.SELL ? (stopLevel - priceObj.bid) : (priceObj.ask - stopLevel); 67 | stopDistance = stopDistance * envVariables.GetScalingFactor(priceObj.symbol); 68 | 69 | var limitDistance = direction == TradeDirection.BUY ? (limitLevel - priceObj.bid) : (priceObj.ask - limitLevel); 70 | limitDistance = limitDistance * envVariables.GetScalingFactor(priceObj.symbol); 71 | 72 | if (stopDistance < 10 || limitDistance < 2) 73 | { 74 | return; 75 | } 76 | 77 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 78 | var size = decimal.Parse(envVariables.tradingSize); 79 | 80 | if(tradeObjs.accountObj.pnl > 3000){ 81 | size=4; 82 | } 83 | 84 | if(tradeObjs.accountObj.pnl > 4000){ 85 | size=5; 86 | } 87 | 88 | if(stopDistance > 100) { 89 | stopDistance=100; 90 | } 91 | 92 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 93 | { 94 | size = size, 95 | stopDistancePips = stopDistance, 96 | limitDistancePips = limitDistance 97 | }; 98 | 99 | this.requestOpenTrade.Request(openOrderRequest); 100 | 101 | ohlcList.RemoveAt(0); 102 | } 103 | 104 | // Surpress CS1998 105 | // Async method lacks 'await' operators and will run synchronously 106 | await Task.CompletedTask; 107 | 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/strategies/drafts/Random-Extra.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Newtonsoft.Json; 6 | using Utilities; 7 | 8 | namespace backtesting_engine_strategies; 9 | 10 | // https://mccaffers.com/randomly_trading/ 11 | public class RandomStrategyExtra : BaseStrategy, IStrategy 12 | { 13 | 14 | private List ohlcList = new List(); 15 | private OhlcObject lastItem = new OhlcObject(); 16 | 17 | public RandomStrategyExtra( IRequestOpenTrade requestOpenTrade, 18 | ITradingObjects tradeObjs, 19 | IEnvironmentVariables envVariables, 20 | ICloseOrder closeOrder, 21 | IWebNotification webNotification ) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) {} 22 | 23 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 24 | public async Task Invoke(PriceObj priceObj) 25 | { 26 | 27 | foreach(var item in this.tradeObjs.openTrades.Where(x => x.Key.Contains(priceObj.symbol)).Select(x => x.Value)){ 28 | // if(priceObj.date.Subtract(item.openDate).TotalHours > 1){ 29 | // this.closeOrder.Request(item, priceObj); 30 | // return; 31 | // } 32 | } 33 | 34 | if(priceObj.date.DayOfWeek == DayOfWeek.Sunday) 35 | { 36 | return; 37 | } 38 | 39 | if(priceObj.date.DayOfWeek == DayOfWeek.Friday && priceObj.date.Hour > 14){ 40 | return; 41 | } 42 | 43 | // if(priceObj.date.Hour < 5 || priceObj.date.Hour > 19){ 44 | // return; 45 | // } 46 | 47 | var randomInt = new Random().Next(2); 48 | // 0 or 1 49 | TradeDirection direction = TradeDirection.BUY; 50 | if (randomInt== 0) 51 | { 52 | direction = TradeDirection.SELL; 53 | } 54 | 55 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 56 | 57 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 58 | { 59 | size = decimal.Parse(envVariables.tradingSize), 60 | stopDistancePips = decimal.Parse(envVariables.stopDistanceInPips), 61 | limitDistancePips = decimal.Parse(envVariables.limitDistanceInPips), 62 | }; 63 | 64 | this.requestOpenTrade.Request(openOrderRequest); 65 | 66 | // Surpress CS1998 67 | // Async method lacks 'await' operators and will run synchronously 68 | await Task.CompletedTask; 69 | 70 | return; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/strategies/drafts/RandomRecentHighLow copy.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine_strategies; 8 | 9 | public class RandomRecentHighLow_COPY : BaseStrategy, IStrategy 10 | { 11 | private List ohlcList = new List(); 12 | 13 | public RandomRecentHighLow_COPY(IRequestOpenTrade requestOpenTrade, IEnvironmentVariables envVariables, ITradingObjects tradeObjs, ICloseOrder closeOrder, IWebNotification webNotification) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) { } 14 | 15 | private OhlcObject lastItem = new OhlcObject(); 16 | 17 | private DateTime lastTraded = DateTime.MinValue; 18 | 19 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 20 | public async Task Invoke(PriceObj priceObj) 21 | { 22 | 23 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromMinutes(30), ohlcList); 24 | 25 | // Maximum of one trade open at a time 26 | // Conditional to only invoke the strategy if there are no trades open 27 | if (tradeObjs.openTrades.Count() >= 1) 28 | { 29 | return; 30 | } 31 | 32 | if(priceObj.date.Subtract(lastTraded).TotalHours < 6){ 33 | return; 34 | } 35 | 36 | 37 | // Keep 5 hours of history (30*10) 38 | // Only start trading once you have 5 hours 39 | if (ohlcList.Count > envVariables.variableA) 40 | { 41 | 42 | lastTraded=priceObj.date; 43 | 44 | // Get the highest value from all of the OHLC objects 45 | var recentHigh = ohlcList.Max(x => x.low); 46 | 47 | // Get the lowest value from all of the OHLC objects 48 | var recentLow = ohlcList.Min(x => x.close); 49 | 50 | var randomInt = new Random().Next(2); // 0 or 1 51 | 52 | // Default to BUY, randomly switch 53 | TradeDirection direction = TradeDirection.BUY; 54 | if (randomInt == 0) 55 | { 56 | direction = TradeDirection.SELL; 57 | } 58 | 59 | var stopLevel = direction == TradeDirection.BUY ? recentLow : recentHigh; 60 | var limitLevel = direction == TradeDirection.BUY ? recentHigh : recentLow; 61 | 62 | var stopDistance = direction == TradeDirection.SELL ? (stopLevel - priceObj.bid) : (priceObj.ask - stopLevel); 63 | stopDistance = stopDistance * envVariables.GetScalingFactor(priceObj.symbol); 64 | 65 | var limitDistance = direction == TradeDirection.BUY ? (limitLevel - priceObj.bid) : (priceObj.ask - limitLevel); 66 | limitDistance = limitDistance * envVariables.GetScalingFactor(priceObj.symbol); 67 | 68 | // System.Console.WriteLine("limit: " + limitDistance + " stop: "+ stopDistance); 69 | // System.Console.WriteLine(limitDistance-stopDistance); 70 | // if(stopDistance < limitDistance){ 71 | // var swapSL = stopDistance; 72 | // stopDistance = limitDistance; 73 | // limitDistance = swapSL; 74 | // direction = direction == TradeDirection.BUY ? TradeDirection.SELL : TradeDirection.BUY; 75 | // } 76 | // Ensure there is enough distance and isn't going to be immediately auto stopped out 77 | if (stopDistance < 10 || limitDistance < 10) 78 | { 79 | return; 80 | } 81 | 82 | // Stop any excessively large stop losses 83 | if(stopDistance > 50){ 84 | // stopDistance = 50; 85 | } 86 | 87 | // Stop any crazy amounts 88 | if(limitDistance > 50){ 89 | // limitDistance = 50; 90 | } 91 | 92 | // System.Console.WriteLine(stopLevel); 93 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 94 | 95 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 96 | { 97 | size = decimal.Parse(envVariables.tradingSize), 98 | stopDistancePips = stopDistance, 99 | limitDistancePips = limitDistance 100 | 101 | }; 102 | 103 | this.requestOpenTrade.Request(openOrderRequest); 104 | 105 | ohlcList.RemoveAt(0); 106 | } 107 | 108 | // Surpress CS1998 109 | // Async method lacks 'await' operators and will run synchronously 110 | await Task.CompletedTask; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/strategies/drafts/RandomRecentHighLow_DRAFT.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine_strategies; 8 | 9 | public class RandomRecentHighLow_DRAFT : BaseStrategy, IStrategy 10 | { 11 | private List ohlcList = new List(); 12 | 13 | public RandomRecentHighLow_DRAFT(IRequestOpenTrade requestOpenTrade, IEnvironmentVariables envVariables, ITradingObjects tradeObjs, ICloseOrder closeOrder, IWebNotification webNotification) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) { } 14 | 15 | private OhlcObject lastItem = new OhlcObject(); 16 | 17 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 18 | public async Task Invoke(PriceObj priceObj) 19 | { 20 | 21 | // Maximum of one trade open at a time 22 | // Conditional to only invoke the strategy if there are no trades open 23 | if (tradeObjs.openTrades.Count() >= 1) 24 | { 25 | return; 26 | } 27 | 28 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromMinutes(30), ohlcList); 29 | 30 | if (ohlcList.Count > 1) 31 | { 32 | var secondLast = ohlcList[ohlcList.Count - 2]; 33 | if (secondLast.complete && secondLast.close != lastItem.close) 34 | { 35 | await webNotification.PriceUpdate(secondLast, true); 36 | lastItem = secondLast; 37 | } 38 | else 39 | { 40 | await webNotification.PriceUpdate(ohlcList.Last()); 41 | } 42 | } 43 | 44 | // Keep 5 hours of history (30*10) 45 | if (ohlcList.Count > 10) 46 | { 47 | 48 | // Get the highest value from all of the OHLC objects 49 | var recentHigh = ohlcList.Max(x => x.high); 50 | 51 | // Get the lowest value from all of the OHLC objects 52 | var recentLow = ohlcList.Min(x => x.low); 53 | 54 | var randomInt = new Random().Next(2); // 0 or 1 55 | 56 | // Default to BUY, randomly switch 57 | TradeDirection direction = TradeDirection.SELL; 58 | if (randomInt == 0) 59 | { 60 | direction = TradeDirection.SELL; 61 | } 62 | 63 | 64 | var highDiff = recentHigh - priceObj.ask; 65 | var lowerDIff = priceObj.ask - recentLow; 66 | 67 | if (highDiff < lowerDIff) 68 | { 69 | direction = TradeDirection.BUY; 70 | } 71 | 72 | var stopDistance = 10m; 73 | var limitLevel = 0m; 74 | 75 | if (direction == TradeDirection.BUY) 76 | { 77 | // stopLevel = recentLow; 78 | limitLevel = recentHigh; 79 | stopDistance = 10; 80 | } 81 | else 82 | { 83 | // stopLevel = recentHigh; 84 | limitLevel = recentLow; 85 | stopDistance = 10; 86 | } 87 | 88 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 89 | 90 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 91 | { 92 | size = decimal.Parse(envVariables.tradingSize), 93 | stopDistancePips = stopDistance, 94 | limitLevel = limitLevel 95 | 96 | }; 97 | 98 | this.requestOpenTrade.Request(openOrderRequest); 99 | 100 | ohlcList.RemoveAt(0); 101 | } 102 | // return true; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/strategies/drafts/SimpleMovingAverage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Globalization; 4 | using System.Threading.Tasks.Dataflow; 5 | using backtesting_engine; 6 | using backtesting_engine.interfaces; 7 | using backtesting_engine_models; 8 | using Newtonsoft.Json; 9 | using Utilities; 10 | 11 | namespace backtesting_engine_strategies; 12 | 13 | 14 | public class SimpleMovingAverage : IStrategy 15 | { 16 | readonly IRequestOpenTrade requestOpenTrade; 17 | readonly IEnvironmentVariables envVariables; 18 | private List ohlcListShortTerm = new List(); 19 | private List ohlcListLongTerm = new List(); 20 | 21 | public SimpleMovingAverage(IRequestOpenTrade requestOpenTrade, IEnvironmentVariables envVariables) 22 | { 23 | this.requestOpenTrade = requestOpenTrade; 24 | this.envVariables = envVariables; 25 | } 26 | 27 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 28 | public Task Invoke(PriceObj priceObj) 29 | { 30 | 31 | ohlcListShortTerm = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromHours(4), ohlcListShortTerm); 32 | ohlcListLongTerm = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromHours(1), ohlcListLongTerm); 33 | 34 | // Keep 30 days of history 35 | if(ohlcListShortTerm.Count > 50){ 36 | 37 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 38 | 39 | var direction = TradeDirection.BUY; 40 | if(priceObj.ask > ohlcListShortTerm.Average(x=>x.close)){ 41 | direction = TradeDirection.SELL; 42 | } 43 | 44 | // if(ohlcListShortTerm.Average(x=>x.close) > ohlcListLongTerm.Average(x=>x.close)){ 45 | // direction = TradeDirection.SELL; 46 | // } 47 | 48 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 49 | { 50 | size = decimal.Parse(envVariables.tradingSize), 51 | limitDistancePips = 100, 52 | stopDistancePips = 100 53 | 54 | }; 55 | 56 | this.requestOpenTrade.Request(openOrderRequest); 57 | 58 | ohlcListShortTerm.RemoveAt(0); 59 | } 60 | return Task.CompletedTask; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/strategies/drafts/VolatilityCalculator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Globalization; 4 | using System.Threading.Tasks.Dataflow; 5 | using backtesting_engine; 6 | using backtesting_engine.interfaces; 7 | using backtesting_engine_models; 8 | using Nest; 9 | using Newtonsoft.Json; 10 | using Utilities; 11 | 12 | namespace backtesting_engine_strategies; 13 | 14 | public class VolatilityCalculator : IStrategy 15 | { 16 | readonly IEnvironmentVariables envVariables; 17 | readonly IElasticClient elasticClient; 18 | 19 | public VolatilityCalculator(IEnvironmentVariables envVariables, IElasticClient elasticClient) 20 | { 21 | this.envVariables = envVariables; 22 | this.elasticClient = elasticClient; 23 | } 24 | 25 | private List ohlcList = new List(); 26 | private List volatilityList = new List(); 27 | private DateTime lastBatchUpdate = DateTime.Now; 28 | private decimal lastPrice = decimal.Zero; 29 | private decimal movement = decimal.Zero; 30 | private List distanceBetweenPriceMoves = new List(); 31 | private List spreadDistance = new List(); 32 | 33 | public Task Invoke(PriceObj priceObj) { 34 | 35 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromDays(1), ohlcList); 36 | 37 | if(lastPrice==decimal.Zero){ 38 | lastPrice=priceObj.ask; 39 | } else if(lastPrice!=priceObj.ask){ 40 | var distance = Math.Abs(lastPrice-priceObj.ask) * envVariables.GetScalingFactor(priceObj.symbol); 41 | movement+=distance; 42 | distanceBetweenPriceMoves.Add(distance); 43 | lastPrice=priceObj.ask; 44 | } 45 | 46 | var spread = Math.Abs(priceObj.ask - priceObj.bid); 47 | spreadDistance.Add(spread);  48 | 49 | // Will only be higher than one count if there is more than one day in the list 50 | if(ohlcList.Count > 1){ 51 | 52 | var dayRange = (ohlcList.First().high - ohlcList.First().low) * envVariables.GetScalingFactor(priceObj.symbol); 53 | var gap = Math.Abs(ohlcList.First().close - ohlcList.Skip(1).First().open) * envVariables.GetScalingFactor(priceObj.symbol); 54 | var dayClose = ohlcList.First().close; 55 | ohlcList.RemoveAt(0); 56 | 57 | var vObject = new VolatilityObject(){ 58 | date = priceObj.date, 59 | symbols = envVariables.symbols, 60 | runID = envVariables.runID, 61 | runIteration = int.Parse(envVariables.runIteration), 62 | strategy = envVariables.strategy, 63 | dayRange = dayRange, 64 | dayClose = dayClose, 65 | totalMovement = movement, 66 | dayCloseGap = gap, 67 | distanceBetweenPriceMoves=distanceBetweenPriceMoves.Average(), 68 | spreadDistance=spreadDistance.Average() * envVariables.GetScalingFactor(priceObj.symbol) 69 | }; 70 | 71 | 72 | distanceBetweenPriceMoves=new List(); 73 | spreadDistance=new List(); 74 | movement=0m; 75 | volatilityList.Add(vObject); 76 | BatchUpdate(); 77 | } 78 | return Task.CompletedTask; 79 | } 80 | 81 | private void BatchUpdate(){ 82 | if(DateTime.Now.Subtract(lastBatchUpdate).TotalSeconds <= 5){ 83 | return; 84 | } 85 | lastBatchUpdate=DateTime.Now; 86 | elasticClient.IndexMany(volatilityList, "volatility"); 87 | volatilityList= new List(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/strategies/locked/Momentum/Momentum_14a2da93.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine_strategies; 8 | 9 | public class Momentum_14a2da93: BaseStrategy, IStrategy 10 | { 11 | private List ohlcList = new List(); 12 | 13 | public Momentum_14a2da93(IRequestOpenTrade requestOpenTrade, IEnvironmentVariables envVariables, ITradingObjects tradeObjs, ICloseOrder closeOrder, IWebNotification webNotification) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) { } 14 | 15 | private OhlcObject lastItem = new OhlcObject(); 16 | 17 | private DateTime lastTraded = DateTime.MinValue; 18 | 19 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 20 | public async Task Invoke(PriceObj priceObj) 21 | { 22 | 23 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromMinutes(120), ohlcList); 24 | 25 | // Maximum of one trade open at a time 26 | // Conditional to only invoke the strategy if there are no trades open 27 | if (tradeObjs.openTrades.Count() >= 1) 28 | { 29 | return; 30 | } 31 | 32 | if(priceObj.date.Subtract(lastTraded).TotalHours < 8){ 33 | return; 34 | } 35 | 36 | 37 | // Keep 5 hours of history (30*10) 38 | // Only start trading once you have 5 hours 39 | if (ohlcList.Count > envVariables.variableA) 40 | { 41 | 42 | lastTraded=priceObj.date; 43 | 44 | // Get the highest value from all of the OHLC objects 45 | var distance = (ohlcList.Last().close - ohlcList.First().close) * envVariables.GetScalingFactor(priceObj.symbol); 46 | var speed = distance / (120 * (envVariables.variableA ?? 0)); 47 | 48 | // System.Console.WriteLine(speed); 49 | 50 | // Too slow 51 | if(Math.Abs(speed) < 1m){ 52 | return; 53 | } 54 | 55 | var direction = TradeDirection.BUY; 56 | if(speed > 0){ 57 | direction = TradeDirection.SELL; 58 | } 59 | 60 | var stopLevel = direction == TradeDirection.BUY ? ohlcList.Min(x => x.low) : 61 | ohlcList.Max(x => x.high); 62 | 63 | var limitLevel = direction == TradeDirection.BUY ? ohlcList.TakeLast(2).Average(x => x.close): 64 | ohlcList.TakeLast(2).Average(x => x.close); 65 | 66 | var stopDistance = direction == TradeDirection.SELL ? (stopLevel - priceObj.bid) : (priceObj.ask - stopLevel); 67 | stopDistance = stopDistance * envVariables.GetScalingFactor(priceObj.symbol); 68 | 69 | var limitDistance = direction == TradeDirection.BUY ? (limitLevel - priceObj.bid) : (priceObj.ask - limitLevel); 70 | limitDistance = limitDistance * envVariables.GetScalingFactor(priceObj.symbol); 71 | 72 | if (stopDistance < 10 || limitDistance < 2) 73 | { 74 | return; 75 | } 76 | 77 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 78 | 79 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 80 | { 81 | size = decimal.Parse(envVariables.tradingSize), 82 | stopDistancePips = stopDistance, 83 | limitDistancePips = limitDistance 84 | }; 85 | 86 | this.requestOpenTrade.Request(openOrderRequest); 87 | 88 | ohlcList.RemoveAt(0); 89 | } 90 | 91 | // Surpress CS1998 92 | // Async method lacks 'await' operators and will run synchronously 93 | await Task.CompletedTask; 94 | 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/strategies/locked/Momentum/Momentum_18d834cc.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine_strategies; 8 | 9 | public class Momentum_18d834cc : BaseStrategy, IStrategy 10 | { 11 | private List ohlcList = new List(); 12 | 13 | public Momentum_18d834cc(IRequestOpenTrade requestOpenTrade, IEnvironmentVariables envVariables, ITradingObjects tradeObjs, ICloseOrder closeOrder, IWebNotification webNotification) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) { } 14 | 15 | private OhlcObject lastItem = new OhlcObject(); 16 | 17 | private DateTime lastTraded = DateTime.MinValue; 18 | 19 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 20 | public async Task Invoke(PriceObj priceObj) 21 | { 22 | 23 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromMinutes(30), ohlcList); 24 | 25 | // Maximum of one trade open at a time 26 | // Conditional to only invoke the strategy if there are no trades open 27 | if (tradeObjs.openTrades.Count() >= 1) 28 | { 29 | return; 30 | } 31 | 32 | if(priceObj.date.Subtract(lastTraded).TotalHours < 8){ 33 | return; 34 | } 35 | 36 | 37 | // Keep 5 hours of history (30*10) 38 | // Only start trading once you have 5 hours 39 | if (ohlcList.Count > envVariables.variableA) 40 | { 41 | 42 | lastTraded=priceObj.date; 43 | 44 | // Get the highest value from all of the OHLC objects 45 | var distance = (ohlcList.Last().close - ohlcList.First().close) * envVariables.GetScalingFactor(priceObj.symbol); 46 | var speed = distance / (30 * (envVariables.variableA ?? 0)); 47 | 48 | // System.Console.WriteLine(speed); 49 | 50 | // Too slow 51 | if(Math.Abs(speed) < 1m){ 52 | return; 53 | } 54 | 55 | var direction = TradeDirection.BUY; 56 | if(speed > 0){ 57 | direction = TradeDirection.SELL; 58 | } 59 | 60 | var stopLevel = direction == TradeDirection.BUY ? ohlcList.Min(x => x.low) : 61 | ohlcList.Max(x => x.high); 62 | 63 | var limitLevel = direction == TradeDirection.BUY ? ohlcList.TakeLast(2).Average(x => x.close): 64 | ohlcList.TakeLast(2).Average(x => x.close); 65 | 66 | var stopDistance = direction == TradeDirection.SELL ? (stopLevel - priceObj.bid) : (priceObj.ask - stopLevel); 67 | stopDistance = stopDistance * envVariables.GetScalingFactor(priceObj.symbol); 68 | 69 | var limitDistance = direction == TradeDirection.BUY ? (limitLevel - priceObj.bid) : (priceObj.ask - limitLevel); 70 | limitDistance = limitDistance * envVariables.GetScalingFactor(priceObj.symbol); 71 | 72 | if (stopDistance < 10 || limitDistance < 2) 73 | { 74 | return; 75 | } 76 | 77 | if(stopDistance > 100){ 78 | stopDistance = 100; 79 | } 80 | 81 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 82 | 83 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 84 | { 85 | size = decimal.Parse(envVariables.tradingSize), 86 | stopDistancePips = stopDistance, 87 | limitDistancePips = limitDistance 88 | }; 89 | 90 | this.requestOpenTrade.Request(openOrderRequest); 91 | 92 | ohlcList.RemoveAt(0); 93 | } 94 | 95 | // Surpress CS1998 96 | // Async method lacks 'await' operators and will run synchronously 97 | await Task.CompletedTask; 98 | 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/strategies/locked/Momentum/Momentum_2ff0b2b2.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine_strategies; 8 | 9 | public class Momentum_2ff0b2b2 : BaseStrategy, IStrategy 10 | { 11 | private List ohlcList = new List(); 12 | 13 | public Momentum_2ff0b2b2(IRequestOpenTrade requestOpenTrade, IEnvironmentVariables envVariables, ITradingObjects tradeObjs, ICloseOrder closeOrder, IWebNotification webNotification) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) { } 14 | 15 | private OhlcObject lastItem = new OhlcObject(); 16 | 17 | private DateTime lastTraded = DateTime.MinValue; 18 | 19 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 20 | public async Task Invoke(PriceObj priceObj) 21 | { 22 | 23 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromMinutes(10), ohlcList); 24 | 25 | // Maximum of one trade open at a time 26 | // Conditional to only invoke the strategy if there are no trades open 27 | if (tradeObjs.openTrades.Count() >= 1) 28 | { 29 | return; 30 | } 31 | 32 | if(priceObj.date.Subtract(lastTraded).TotalHours < 8){ 33 | return; 34 | } 35 | 36 | 37 | // Keep 5 hours of history (30*10) 38 | // Only start trading once you have 5 hours 39 | if (ohlcList.Count > envVariables.variableA) 40 | { 41 | 42 | lastTraded=priceObj.date; 43 | 44 | // Get the highest value from all of the OHLC objects 45 | var distance = (ohlcList.Last().close - ohlcList.First().close) * envVariables.GetScalingFactor(priceObj.symbol); 46 | var speed = distance / (10 * (envVariables.variableA ?? 0)); 47 | 48 | // System.Console.WriteLine(speed); 49 | 50 | // Too slow 51 | if(Math.Abs(speed) < 1m){ 52 | return; 53 | } 54 | 55 | var direction = TradeDirection.BUY; 56 | if(speed > 0){ 57 | direction = TradeDirection.SELL; 58 | } 59 | 60 | var stopLevel = direction == TradeDirection.BUY ? ohlcList.Min(x => x.low) : 61 | ohlcList.Max(x => x.high); 62 | 63 | var limitLevel = direction == TradeDirection.BUY ? ohlcList.TakeLast(2).Average(x => x.high): 64 | ohlcList.TakeLast(2).Average(x => x.low); 65 | 66 | var stopDistance = direction == TradeDirection.SELL ? (stopLevel - priceObj.bid) : (priceObj.ask - stopLevel); 67 | stopDistance = stopDistance * envVariables.GetScalingFactor(priceObj.symbol); 68 | 69 | var limitDistance = direction == TradeDirection.BUY ? (limitLevel - priceObj.bid) : (priceObj.ask - limitLevel); 70 | limitDistance = limitDistance * envVariables.GetScalingFactor(priceObj.symbol); 71 | 72 | if (stopDistance < 10 || limitDistance < 2) 73 | { 74 | return; 75 | } 76 | 77 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 78 | 79 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 80 | { 81 | size = decimal.Parse(envVariables.tradingSize), 82 | stopDistancePips = stopDistance, 83 | limitDistancePips = limitDistance 84 | }; 85 | 86 | this.requestOpenTrade.Request(openOrderRequest); 87 | 88 | ohlcList.RemoveAt(0); 89 | } 90 | 91 | // Surpress CS1998 92 | // Async method lacks 'await' operators and will run synchronously 93 | await Task.CompletedTask; 94 | 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/strategies/locked/Momentum/Momentum_438539dc.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine_strategies; 8 | 9 | public class Momentum_438539dc : BaseStrategy, IStrategy 10 | { 11 | private List ohlcList = new List(); 12 | 13 | public Momentum_438539dc(IRequestOpenTrade requestOpenTrade, IEnvironmentVariables envVariables, ITradingObjects tradeObjs, ICloseOrder closeOrder, IWebNotification webNotification) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) { } 14 | 15 | private OhlcObject lastItem = new OhlcObject(); 16 | 17 | private DateTime lastTraded = DateTime.MinValue; 18 | 19 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 20 | public async Task Invoke(PriceObj priceObj) 21 | { 22 | 23 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromMinutes(30), ohlcList); 24 | 25 | // Maximum of one trade open at a time 26 | // Conditional to only invoke the strategy if there are no trades open 27 | if (tradeObjs.openTrades.Count() >= 1) 28 | { 29 | return; 30 | } 31 | 32 | if(priceObj.date.Subtract(lastTraded).TotalHours < 8){ 33 | return; 34 | } 35 | 36 | 37 | // Keep 5 hours of history (30*10) 38 | // Only start trading once you have 5 hours 39 | if (ohlcList.Count > envVariables.variableA) 40 | { 41 | 42 | lastTraded=priceObj.date; 43 | 44 | // Get the highest value from all of the OHLC objects 45 | var distance = (ohlcList.Last().close - ohlcList.First().close) * envVariables.GetScalingFactor(priceObj.symbol); 46 | var speed = distance / (30 * (envVariables.variableA ?? 0)); 47 | 48 | // System.Console.WriteLine(speed); 49 | 50 | // Too slow 51 | if(Math.Abs(speed) < 1m){ 52 | return; 53 | } 54 | 55 | var direction = TradeDirection.BUY; 56 | if(speed > 0){ 57 | direction = TradeDirection.SELL; 58 | } 59 | 60 | var stopLevel = direction == TradeDirection.BUY ? ohlcList.Min(x => x.low) : 61 | ohlcList.Max(x => x.high); 62 | 63 | var limitLevel = direction == TradeDirection.BUY ? ohlcList.TakeLast(2).Average(x => x.high): 64 | ohlcList.TakeLast(2).Average(x => x.low); 65 | 66 | var stopDistance = direction == TradeDirection.SELL ? (stopLevel - priceObj.bid) : (priceObj.ask - stopLevel); 67 | stopDistance = stopDistance * envVariables.GetScalingFactor(priceObj.symbol); 68 | 69 | var limitDistance = direction == TradeDirection.BUY ? (limitLevel - priceObj.bid) : (priceObj.ask - limitLevel); 70 | limitDistance = limitDistance * envVariables.GetScalingFactor(priceObj.symbol); 71 | 72 | if (stopDistance < 10 || limitDistance < 2) 73 | { 74 | return; 75 | } 76 | 77 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 78 | 79 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 80 | { 81 | size = decimal.Parse(envVariables.tradingSize), 82 | stopDistancePips = stopDistance, 83 | limitDistancePips = limitDistance 84 | }; 85 | 86 | this.requestOpenTrade.Request(openOrderRequest); 87 | 88 | ohlcList.RemoveAt(0); 89 | } 90 | 91 | // Surpress CS1998 92 | // Async method lacks 'await' operators and will run synchronously 93 | await Task.CompletedTask; 94 | 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/strategies/locked/Momentum/Momentum_4d991e3b.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine_strategies; 8 | 9 | public class Momentum_4d991e3b : BaseStrategy, IStrategy 10 | { 11 | private List ohlcList = new List(); 12 | 13 | public Momentum_4d991e3b(IRequestOpenTrade requestOpenTrade, IEnvironmentVariables envVariables, ITradingObjects tradeObjs, ICloseOrder closeOrder, IWebNotification webNotification) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) { } 14 | 15 | private OhlcObject lastItem = new OhlcObject(); 16 | 17 | private DateTime lastTraded = DateTime.MinValue; 18 | 19 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 20 | public async Task Invoke(PriceObj priceObj) 21 | { 22 | 23 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromMinutes(30), ohlcList); 24 | 25 | // Maximum of one trade open at a time 26 | // Conditional to only invoke the strategy if there are no trades open 27 | if (tradeObjs.openTrades.Count() >= 1) 28 | { 29 | return; 30 | } 31 | 32 | if(priceObj.date.Subtract(lastTraded).TotalHours < 8){ 33 | return; 34 | } 35 | 36 | 37 | // Keep 5 hours of history (30*10) 38 | // Only start trading once you have 5 hours 39 | if (ohlcList.Count > envVariables.variableA) 40 | { 41 | 42 | lastTraded=priceObj.date; 43 | 44 | // Get the highest value from all of the OHLC objects 45 | var distance = (ohlcList.Last().close - ohlcList.First().close) * envVariables.GetScalingFactor(priceObj.symbol); 46 | var speed = distance / (30 * (envVariables.variableA ?? 0)); 47 | 48 | // System.Console.WriteLine(speed); 49 | 50 | // Too slow 51 | if(Math.Abs(speed) < 1m){ 52 | return; 53 | } 54 | 55 | var direction = TradeDirection.BUY; 56 | if(speed > 0){ 57 | direction = TradeDirection.SELL; 58 | } 59 | 60 | var stopLevel = direction == TradeDirection.BUY ? ohlcList.Min(x => x.low) : 61 | ohlcList.Max(x => x.high); 62 | 63 | var limitLevel = direction == TradeDirection.BUY ? ohlcList.TakeLast(2).Average(x => x.close): 64 | ohlcList.TakeLast(2).Average(x => x.close); 65 | 66 | var stopDistance = direction == TradeDirection.SELL ? (stopLevel - priceObj.bid) : (priceObj.ask - stopLevel); 67 | stopDistance = stopDistance * envVariables.GetScalingFactor(priceObj.symbol); 68 | 69 | var limitDistance = direction == TradeDirection.BUY ? (limitLevel - priceObj.bid) : (priceObj.ask - limitLevel); 70 | limitDistance = limitDistance * envVariables.GetScalingFactor(priceObj.symbol); 71 | 72 | if (stopDistance < 10 || limitDistance < 2) 73 | { 74 | return; 75 | } 76 | 77 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 78 | 79 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 80 | { 81 | size = decimal.Parse(envVariables.tradingSize), 82 | stopDistancePips = stopDistance, 83 | limitDistancePips = limitDistance 84 | }; 85 | 86 | this.requestOpenTrade.Request(openOrderRequest); 87 | 88 | ohlcList.RemoveAt(0); 89 | } 90 | 91 | // Surpress CS1998 92 | // Async method lacks 'await' operators and will run synchronously 93 | await Task.CompletedTask; 94 | 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/strategies/locked/Momentum/Momentum_59ca71ff.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine_strategies; 8 | 9 | public class Momentum_59ca71ff : BaseStrategy, IStrategy 10 | { 11 | private List ohlcList = new List(); 12 | 13 | public Momentum_59ca71ff(IRequestOpenTrade requestOpenTrade, IEnvironmentVariables envVariables, ITradingObjects tradeObjs, ICloseOrder closeOrder, IWebNotification webNotification) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) { } 14 | 15 | private OhlcObject lastItem = new OhlcObject(); 16 | 17 | private DateTime lastTraded = DateTime.MinValue; 18 | 19 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 20 | public async Task Invoke(PriceObj priceObj) 21 | { 22 | 23 | var period = envVariables.variableB ?? throw new Exception(); 24 | var maxSpeed = envVariables.variableC ?? throw new Exception(); 25 | var maxStopValue = envVariables.variableD ?? throw new Exception(); 26 | 27 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromMinutes(Decimal.ToDouble(period)), ohlcList); 28 | 29 | // Maximum of one trade open at a time 30 | // Conditional to only invoke the strategy if there are no trades open 31 | if (tradeObjs.openTrades.Count() >= 1) 32 | { 33 | return; 34 | } 35 | 36 | if(priceObj.date.Subtract(lastTraded).TotalHours < 24){ 37 | return; 38 | } 39 | 40 | 41 | // Keep 5 hours of history (30*10) 42 | // Only start trading once you have 5 hours 43 | if (ohlcList.Count > envVariables.variableA) 44 | { 45 | 46 | lastTraded=priceObj.date; 47 | 48 | // Get the highest value from all of the OHLC objects 49 | var distance = (ohlcList.Last().close - ohlcList.First().close) * envVariables.GetScalingFactor(priceObj.symbol); 50 | var speed = distance / (period * (envVariables.variableA ?? 0)); 51 | 52 | // System.Console.WriteLine(speed); 53 | 54 | // Too slow 55 | if(Math.Abs(speed) < maxSpeed){ 56 | return; 57 | } 58 | 59 | var direction = TradeDirection.BUY; 60 | if(speed > 0){ 61 | direction = TradeDirection.SELL; 62 | } 63 | 64 | var stopLevel = direction == TradeDirection.BUY ? ohlcList.Min(x => x.low) : 65 | ohlcList.Max(x => x.high); 66 | 67 | var limitLevel = direction == TradeDirection.BUY ? ohlcList.TakeLast(2).Average(x => x.high): 68 | ohlcList.TakeLast(2).Average(x => x.low); 69 | 70 | var stopDistance = direction == TradeDirection.SELL ? (stopLevel - priceObj.bid) : (priceObj.ask - stopLevel); 71 | stopDistance = stopDistance * envVariables.GetScalingFactor(priceObj.symbol); 72 | 73 | var limitDistance = direction == TradeDirection.BUY ? (limitLevel - priceObj.bid) : (priceObj.ask - limitLevel); 74 | limitDistance = limitDistance * envVariables.GetScalingFactor(priceObj.symbol); 75 | 76 | if (stopDistance < 10 || limitDistance < 2) 77 | { 78 | return; 79 | } 80 | 81 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 82 | var size = decimal.Parse(envVariables.tradingSize); 83 | 84 | if(stopDistance > maxStopValue) { 85 | stopDistance = maxStopValue; 86 | } 87 | 88 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 89 | { 90 | size = size, 91 | stopDistancePips = stopDistance, 92 | limitDistancePips = limitDistance 93 | }; 94 | 95 | this.requestOpenTrade.Request(openOrderRequest); 96 | 97 | ohlcList.RemoveAt(0); 98 | } 99 | 100 | // Surpress CS1998 101 | // Async method lacks 'await' operators and will run synchronously 102 | await Task.CompletedTask; 103 | 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/strategies/locked/Momentum/Momentum_7aa778ee.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine_strategies; 8 | 9 | public class Momentum_7aa778ee : BaseStrategy, IStrategy 10 | { 11 | private List ohlcList = new List(); 12 | 13 | public Momentum_7aa778ee(IRequestOpenTrade requestOpenTrade, IEnvironmentVariables envVariables, ITradingObjects tradeObjs, ICloseOrder closeOrder, IWebNotification webNotification) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) { } 14 | 15 | private OhlcObject lastItem = new OhlcObject(); 16 | 17 | private DateTime lastTraded = DateTime.MinValue; 18 | 19 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 20 | public async Task Invoke(PriceObj priceObj) 21 | { 22 | 23 | var totalOHLCCount = decimal.ToInt32(envVariables.variableA ?? throw new Exception()); 24 | var period = envVariables.variableB ?? throw new Exception(); 25 | var maxSpeed = envVariables.variableC ?? throw new Exception(); 26 | var maxStopValue = 1000; 27 | var takeLast = decimal.ToInt32(envVariables.variableE ?? throw new Exception()); 28 | 29 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromMinutes(Decimal.ToDouble(period)), ohlcList); 30 | 31 | // Maximum of one trade open at a time 32 | // Conditional to only invoke the strategy if there are no trades open 33 | if (tradeObjs.openTrades.Count() >= 1) 34 | { 35 | return; 36 | } 37 | 38 | if(priceObj.date.Subtract(lastTraded).TotalHours < 24){ 39 | return; 40 | } 41 | 42 | // Keep 5 hours of history (30*10) 43 | // Only start trading once you have 5 hours 44 | if (ohlcList.Count >= envVariables.variableA) 45 | { 46 | 47 | // System.Console.WriteLine(ohlcList.Count); 48 | var localOHLCList = ohlcList.TakeLast(totalOHLCCount); 49 | 50 | lastTraded=priceObj.date; 51 | 52 | // Get the highest value from all of the OHLC objects 53 | var distance = (ohlcList.Last().close - ohlcList.First().close) * envVariables.GetScalingFactor(priceObj.symbol); 54 | var speed = distance / (period * (envVariables.variableA ?? 0)); 55 | 56 | // Too slow 57 | if(Math.Abs(speed) < maxSpeed){ 58 | return; 59 | } 60 | 61 | var direction = TradeDirection.BUY; 62 | if(speed > 0){ 63 | direction = TradeDirection.SELL; 64 | } 65 | 66 | var stopLevel = direction == TradeDirection.BUY ? ohlcList.Min(x => x.low) : 67 | ohlcList.Max(x => x.high); 68 | 69 | var limitLevel = direction == TradeDirection.BUY ? ohlcList.TakeLast(takeLast).Average(x => x.high): 70 | ohlcList.TakeLast(takeLast).Average(x => x.low); 71 | 72 | var stopDistance = direction == TradeDirection.SELL ? (stopLevel - priceObj.bid) : (priceObj.ask - stopLevel); 73 | stopDistance = stopDistance * envVariables.GetScalingFactor(priceObj.symbol); 74 | 75 | var limitDistance = direction == TradeDirection.BUY ? (limitLevel - priceObj.bid) : (priceObj.ask - limitLevel); 76 | limitDistance = limitDistance * envVariables.GetScalingFactor(priceObj.symbol); 77 | 78 | if (stopDistance < 10 || limitDistance < 2) 79 | { 80 | return; 81 | } 82 | 83 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 84 | var size = decimal.Parse(envVariables.tradingSize); 85 | 86 | if(stopDistance > maxStopValue) { 87 | stopDistance = maxStopValue; 88 | } 89 | 90 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 91 | { 92 | size = size, 93 | stopDistancePips = stopDistance, 94 | limitDistancePips = limitDistance 95 | }; 96 | 97 | this.requestOpenTrade.Request(openOrderRequest); 98 | 99 | ohlcList.RemoveAll( x=>x.complete); 100 | } 101 | 102 | // Surpress CS1998 103 | // Async method lacks 'await' operators and will run synchronously 104 | await Task.CompletedTask; 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/strategies/locked/Random/Random.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine_strategies; 8 | 9 | // Read about this strategy on https://mccaffers.com/randomly_trading/ 10 | public class RandomStrategy : BaseStrategy, IStrategy 11 | { 12 | // Dependency injection pulls a number of classes 13 | // This allows the reuse in different environments (eg. backtesting and live) 14 | public RandomStrategy(IRequestOpenTrade requestOpenTrade, 15 | ITradingObjects tradeObjs, 16 | IEnvironmentVariables envVariables, 17 | ICloseOrder closeOrder, 18 | IWebNotification webNotification) : 19 | base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) 20 | { 21 | // Empty constructor, just used to inject the dependencies 22 | } 23 | 24 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 25 | public async Task Invoke(PriceObj priceObj) 26 | { 27 | 28 | // Maximum of one trade open at a time 29 | // Conditional to only invoke the strategy if there are no trades open 30 | if (tradeObjs.openTrades.Count() >= 1) 31 | { 32 | return; 33 | } 34 | 35 | // Generate a random number betwee 0 and 1 36 | var randomInt = new Random().Next(2); 37 | 38 | // Default to a BUY direction 39 | TradeDirection direction = TradeDirection.BUY; 40 | 41 | // Depending on the random integer, switch to SELL 42 | if (randomInt == 0) 43 | { 44 | direction = TradeDirection.SELL; 45 | } 46 | 47 | // Generate a key for the new trade 48 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 49 | 50 | // Build a request object 51 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 52 | { 53 | size = decimal.Parse(envVariables.tradingSize), 54 | stopDistancePips = decimal.Parse(envVariables.stopDistanceInPips), 55 | limitDistancePips = decimal.Parse(envVariables.limitDistanceInPips), 56 | }; 57 | 58 | 59 | // Open a trade request 60 | requestOpenTrade.Request(openOrderRequest); 61 | 62 | // Surpress CS1998 63 | // Async method lacks 'await' operators and will run synchronously 64 | await Task.CompletedTask; 65 | 66 | return; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/strategies/locked/Random/RandomRecentHighLow.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using backtesting_engine; 3 | using backtesting_engine.interfaces; 4 | using backtesting_engine_models; 5 | using Utilities; 6 | 7 | namespace backtesting_engine_strategies; 8 | 9 | public class RandomRecentHighLow : BaseStrategy, IStrategy 10 | { 11 | private List ohlcList = new List(); 12 | 13 | public RandomRecentHighLow(IRequestOpenTrade requestOpenTrade, IEnvironmentVariables envVariables, ITradingObjects tradeObjs, ICloseOrder closeOrder, IWebNotification webNotification) : base(requestOpenTrade, tradeObjs, envVariables, closeOrder, webNotification) { } 14 | 15 | private OhlcObject lastItem = new OhlcObject(); 16 | 17 | private DateTime lastTraded = DateTime.MinValue; 18 | 19 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 20 | public async Task Invoke(PriceObj priceObj) 21 | { 22 | 23 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromMinutes(30), ohlcList); 24 | 25 | // Maximum of one trade open at a time 26 | // Conditional to only invoke the strategy if there are no trades open 27 | if (tradeObjs.openTrades.Count() >= 3) 28 | { 29 | return; 30 | } 31 | 32 | if(priceObj.date.Subtract(lastTraded).TotalHours < 8){ 33 | return; 34 | } 35 | 36 | 37 | // Keep 5 hours of history (30*10) 38 | // Only start trading once you have 5 hours 39 | if (ohlcList.Count > envVariables.variableA) 40 | { 41 | 42 | lastTraded=priceObj.date; 43 | 44 | // Get the highest value from all of the OHLC objects 45 | var recentHigh = ohlcList.Max(x => x.high); 46 | 47 | // Get the lowest value from all of the OHLC objects 48 | var recentLow = ohlcList.Min(x => x.low); 49 | 50 | var randomInt = new Random().Next(2); // 0 or 1 51 | 52 | // Default to BUY, randomly switch 53 | TradeDirection direction = TradeDirection.BUY; 54 | if (randomInt == 0) 55 | { 56 | direction = TradeDirection.SELL; 57 | } 58 | 59 | var stopLevel = direction == TradeDirection.BUY ? recentLow : recentHigh; 60 | var limitLevel = direction == TradeDirection.BUY ? ohlcList.TakeLast(2).Max(x => x.high) : 61 | ohlcList.TakeLast(2).Min(x => x.low); 62 | 63 | var stopDistance = direction == TradeDirection.SELL ? (stopLevel - priceObj.bid) : (priceObj.ask - stopLevel); 64 | stopDistance = stopDistance * envVariables.GetScalingFactor(priceObj.symbol); 65 | 66 | var limitDistance = direction == TradeDirection.BUY ? (limitLevel - priceObj.bid) : (priceObj.ask - limitLevel); 67 | limitDistance = limitDistance * envVariables.GetScalingFactor(priceObj.symbol); 68 | 69 | // Ensure there is enough distance and isn't going to be immediately auto stopped out 70 | if (stopDistance < 20 || limitDistance < 20) 71 | { 72 | return; 73 | } 74 | 75 | // Stop any excessive stop losses 76 | if(stopDistance > 100){ 77 | stopDistance = 100; 78 | } 79 | 80 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 81 | 82 | var openOrderRequest = new RequestObject(priceObj, direction, envVariables, key) 83 | { 84 | size = decimal.Parse(envVariables.tradingSize), 85 | stopDistancePips = stopDistance, 86 | limitDistancePips = limitDistance 87 | 88 | }; 89 | 90 | this.requestOpenTrade.Request(openOrderRequest); 91 | 92 | ohlcList.RemoveAt(0); 93 | } 94 | 95 | 96 | // Surpress CS1998 97 | // Async method lacks 'await' operators and will run synchronously 98 | await Task.CompletedTask; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/strategies/strategies.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@microsoft/signalr": "^8.0.0", 7 | "@microsoft/signalr-protocol-msgpack": "^8.0.0", 8 | "@testing-library/jest-dom": "^4.2.4", 9 | "@testing-library/react": "^9.5.0", 10 | "@testing-library/user-event": "^7.2.1", 11 | "apexcharts": "^3.35.4", 12 | "react": "^16.13.1", 13 | "react-apexcharts": "^1.4.0", 14 | "react-dom": "^16.13.1", 15 | "react-scripts": "^5.0.1" 16 | }, 17 | "scripts": { 18 | "start": "BROWSER=FIREFOX react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": "react-app" 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccaffers/backtesting-engine/c9c8f26a0fa886edac2e6118f6003e49acf06376/src/ui/public/favicon.ico -------------------------------------------------------------------------------- /src/ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/ui/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccaffers/backtesting-engine/c9c8f26a0fa886edac2e6118f6003e49acf06376/src/ui/public/logo192.png -------------------------------------------------------------------------------- /src/ui/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccaffers/backtesting-engine/c9c8f26a0fa886edac2e6118f6003e49acf06376/src/ui/public/logo512.png -------------------------------------------------------------------------------- /src/ui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/ui/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/ui/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Chat from './TradingUI/Dashboard'; 3 | 4 | function App() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | 12 | export default App; 13 | -------------------------------------------------------------------------------- /src/ui/src/Functions/DisplayOHLCData.js: -------------------------------------------------------------------------------- 1 | function UpdateChart(OHLCObj, chartRef, setSeries){ 2 | 3 | const eventDate = +new Date(OHLCObj.d); 4 | 5 | setSeries((previousState) => { 6 | let previousDataPoints = previousState.data[0].dataPoints; 7 | 8 | let indexValue = previousDataPoints.findIndex((obj => obj.x===eventDate)); 9 | // -1 means it doesn't exist, lets start a new element 10 | let priceEvent = [ OHLCObj.o, OHLCObj.h, OHLCObj.l, OHLCObj.c]; 11 | if(indexValue==-1){ 12 | const keepAmount = 20; 13 | if(previousDataPoints.length>keepAmount){ 14 | previousDataPoints = previousDataPoints.slice(previousDataPoints.length-keepAmount); 15 | } 16 | previousDataPoints = [...previousDataPoints, { y: priceEvent, x: eventDate} ]; 17 | indexValue = previousDataPoints.length-1; 18 | } else { 19 | // dataPoints.current[indexValue.current].x = dataPoints.current[indexValue.current].x; 20 | previousDataPoints[indexValue].y = priceEvent; 21 | } 22 | let color = "#FF0000"; // red 23 | if(previousDataPoints[indexValue].y[3]>previousDataPoints[indexValue].y[0]){ 24 | color="#00D100"; // green 25 | } 26 | previousDataPoints[indexValue].color = color; 27 | 28 | previousState.data[0].dataPoints = previousDataPoints; 29 | let candleSticks = previousState.data[0]; 30 | 31 | // Remove old trades from the UI 32 | let alignedState = []; 33 | 34 | for(const item of previousState.data){ 35 | if(item.type === "line"){ 36 | alignedState.push({ 37 | _tradingType: item._tradingType, 38 | _tradingKey: item._tradingKey, 39 | type: "line", 40 | color:item.color, 41 | dataPoints: item.dataPoints.filter(e => e.x > candleSticks.dataPoints[0].x)}); 42 | } 43 | } 44 | previousState.data = [candleSticks, ...alignedState] 45 | 46 | return previousState; 47 | }); 48 | 49 | chartRef.current.render(); 50 | } 51 | 52 | export default UpdateChart; 53 | -------------------------------------------------------------------------------- /src/ui/src/Functions/SaveClosedTrades.js: -------------------------------------------------------------------------------- 1 | 2 | function SaveClosedTradeForTable(OHLCObj, setClosedTrades){ 3 | setClosedTrades((previousState) => { 4 | previousState.push(OHLCObj); 5 | return previousState; 6 | }); 7 | } 8 | 9 | export default SaveClosedTradeForTable; 10 | -------------------------------------------------------------------------------- /src/ui/src/Functions/TradesOnLinearXAxis.js: -------------------------------------------------------------------------------- 1 | function AddHedgingTrade(OHLCObj, tradeType, setSeries2, hedgeCount, chartRef2){ 2 | 3 | if(hedgeCount.current>50){ 4 | hedgeCount.current=0; 5 | 6 | setSeries2((previousState) => { 7 | previousState.data = []; 8 | return previousState; 9 | }); 10 | } 11 | 12 | setSeries2((previousState) => { 13 | 14 | let color = '#007500'; // green 15 | if(OHLCObj.profit < 0){ 16 | color ='#750000'; 17 | } 18 | let direction = "BUY"; 19 | if(OHLCObj.direction === 1){ 20 | direction="SELL"; 21 | } 22 | 23 | let newState = previousState; 24 | 25 | 26 | // Loop all data obj's, if the _date is older than 3 seconds, delete it 27 | // newState.data = newState.data.filter(function(item) { 28 | // if(item._date == null){ 29 | // return true; 30 | // } 31 | // if(item._tradingType === "closed") 32 | // { 33 | // return false; 34 | // } 35 | // let localDate=new Date(item._date.getTime()); 36 | // localDate.setSeconds(localDate.getSeconds()+60); 37 | // return localDate.getTime() > new Date().getTime(); 38 | // }) 39 | 40 | if(OHLCObj.closeLevel === 0){ 41 | OHLCObj.closeLevel = OHLCObj.level; 42 | } 43 | 44 | 45 | // Lets check if the open trade exists, only need to add it once 46 | if(newState.data.some(item => item._tradingKey === OHLCObj.key)){ 47 | 48 | const index = newState.data.findIndex((element, index) => { 49 | if (element._tradingKey === OHLCObj.key) { 50 | return true 51 | } 52 | }); 53 | 54 | if(index!==-1){ 55 | 56 | let color = '#007500'; // green 57 | if(OHLCObj.profit < 0){ 58 | color ='#750000'; 59 | } 60 | // if(newState.data[index].dataPoints.length == 1){ 61 | // newState.data[index].dataPoints[0].x = hedgeCount.current; 62 | // newState.data[index].dataPoints[0].y = OHLCObj.level; 63 | 64 | // newState.data[index].dataPoints.push([]); 65 | // } 66 | newState.data[index].color = color; 67 | console.log(newState.data[index]) 68 | newState.data[index].dataPoints[1].x = newState.data[index].dataPoints[0].x+2; 69 | newState.data[index].dataPoints[1].y = OHLCObj.closeLevel; 70 | return newState; 71 | } 72 | } 73 | 74 | hedgeCount.current++; 75 | 76 | // Otherwise, add the open trades that doesn't exist, or a new closed trade 77 | newState.data.push({ 78 | _tradingType: tradeType, 79 | _date : new Date(), 80 | _tradingKey: OHLCObj.key, 81 | type: "line", 82 | color:color, 83 | dataPoints: [ 84 | { x: hedgeCount.current, y: OHLCObj.level, indexLabel: direction }, 85 | { x: hedgeCount.current, y: OHLCObj.closeLevel } 86 | ] 87 | }); 88 | 89 | return newState; 90 | 91 | }); 92 | chartRef2.current.render(); 93 | } 94 | 95 | export default AddHedgingTrade; 96 | -------------------------------------------------------------------------------- /src/ui/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | 15 | .wrapper { 16 | display: flex; 17 | flex-flow: row wrap; 18 | text-align: center; 19 | } 20 | 21 | .wrapper > * { 22 | padding: 5px; 23 | flex: 1 100%; 24 | } 25 | 26 | .header { 27 | /* background: tomato; */ 28 | font-size:24px; 29 | } 30 | 31 | .footer { 32 | /* background: lightgreen; */ 33 | } 34 | 35 | .main { 36 | text-align: left; 37 | 38 | /* background: deepskyblue; */ 39 | } 40 | 41 | .chartDiv { 42 | margin-bottom:20px; 43 | flex: 1 60%; 44 | } 45 | 46 | .chartDiv2 { 47 | margin-bottom:20px; 48 | flex: 1 38%; 49 | } 50 | 51 | table { 52 | width:100%; 53 | } 54 | 55 | td { 56 | border: 1px solid black; 57 | } 58 | 59 | tr { 60 | line-height: 14px; 61 | } 62 | thead { 63 | font-weight: 500; 64 | } 65 | 66 | .openTradesDiv { 67 | margin-top:10px; 68 | } 69 | 70 | .aside-1 { 71 | /* background: gold; */ 72 | } 73 | 74 | .aside-2 { 75 | /* background: hotpink; */ 76 | } 77 | 78 | @media all and (min-width: 600px) { 79 | .aside { flex: 1 0 0; } 80 | } 81 | 82 | @media all and (min-width: 800px) { 83 | .main { flex: 5 0px; } 84 | .aside-1 { order: 1; } 85 | .main { order: 2; } 86 | .aside-2 { order: 3; } 87 | .footer { order: 4; } 88 | } 89 | 90 | body { 91 | padding: 0.5em; 92 | } 93 | 94 | .accountLabel { 95 | margin-top:10px; 96 | font-size:30px; 97 | } -------------------------------------------------------------------------------- /src/ui/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /src/utilities/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | namespace Utilities; 2 | 3 | public static class ConsoleLogger { 4 | 5 | private static readonly bool systemLog; 6 | private static readonly bool consoleLog; 7 | private static readonly bool lambdaLog; 8 | 9 | static ConsoleLogger () { 10 | _ = bool.TryParse(EnvironmentVariables.Get("systemLog", true), out systemLog); 11 | _ = bool.TryParse(EnvironmentVariables.Get("consoleLog", true), out consoleLog); 12 | _ = bool.TryParse(EnvironmentVariables.Get("lambdaLog", true), out lambdaLog); 13 | } 14 | 15 | public static void SystemLog(string message){ 16 | if(systemLog){ 17 | System.Console.WriteLine(message); 18 | } 19 | } 20 | 21 | public static void Log(string message){ 22 | if(consoleLog){ 23 | System.Console.WriteLine(message); 24 | } 25 | } 26 | 27 | public static void Lambda(string symbol, string message){ 28 | if(lambdaLog){ 29 | System.Console.WriteLine(symbol + " - " + message); 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/utilities/DictionaryKeyStrings.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace Utilities; 4 | 5 | public static class DictionaryKeyStrings 6 | { 7 | [SuppressMessage("Sonar Code Smell", "S2245:Using pseudorandom number generators (PRNGs) is security-sensitive", Justification = "Random function has no security use")] 8 | public static string OpenTrade(string symbol, DateTime date){ 9 | var randomInt = new Random().Next(200); 10 | return symbol + "-" + date + "-" + randomInt; 11 | } 12 | 13 | public static string CloseTradeKey(string symbol, DateTime openDate, decimal level){ 14 | return ""+symbol+"-"+openDate+"-"+level; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/utilities/GenericOHLC.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine; 2 | 3 | namespace Utilities 4 | { 5 | 6 | public static class GenericOhlc 7 | { 8 | 9 | public static List CalculateOHLC(PriceObj priceObj, decimal price, TimeSpan duration, List OHLCArray) { 10 | 11 | // If the array is empty lets start populating 12 | if(OHLCArray.Count == 0){ 13 | OHLCArray.Add(new OhlcObject(){ 14 | date=priceObj.date, 15 | open=price, 16 | high=price, 17 | low=price, 18 | close=price 19 | }); 20 | } 21 | 22 | var index = OHLCArray.Count-1; // Get the last item 23 | 24 | // if we hit the minute threshold, build a new OHLC object 25 | var diff = priceObj.date.Subtract(OHLCArray[index].date).TotalMinutes; 26 | if(diff > duration.TotalMinutes){ 27 | OHLCArray[index].complete = true; 28 | OHLCArray[index].close = price; 29 | 30 | OHLCArray.Add(new OhlcObject(){ 31 | date=priceObj.date, 32 | open=price, 33 | high=price, 34 | low=price, 35 | close=price 36 | }); 37 | index = OHLCArray.Count-1; 38 | } 39 | 40 | if(price > OHLCArray[index].high){ 41 | OHLCArray[index].high=price; 42 | } 43 | 44 | if(price < OHLCArray[index].low){ 45 | OHLCArray[index].low=price; 46 | } 47 | 48 | OHLCArray[index].close=price; 49 | 50 | return OHLCArray; 51 | } 52 | 53 | } 54 | } -------------------------------------------------------------------------------- /src/utilities/ServiceExtension.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine.interfaces; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Utilities; 5 | 6 | public static class ServiceExtension { 7 | 8 | public static IServiceCollection RegisterStrategies(this IServiceCollection services, IEnvironmentVariables variables) 9 | { 10 | foreach(var i in variables.strategy.Split(",")){ 11 | var _type = Type.GetType("backtesting_engine_strategies." + i + ",strategies") ?? default(Type); 12 | if(_type is not null && typeof(IStrategy).IsAssignableFrom(_type) ){ 13 | services.AddSingleton(typeof(IStrategy), _type); 14 | } 15 | } 16 | 17 | CheckStrategyExistsAndAreOfTypeIStragey(services); 18 | return services; 19 | } 20 | 21 | private static void CheckStrategyExistsAndAreOfTypeIStragey(IServiceCollection services){ 22 | if (!services.Any(x => x.ServiceType == typeof(IStrategy))){ 23 | throw new ArgumentException("No Strategies defined"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/utilities/utilities.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/webserver/Controllers/ChatController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.SignalR; 3 | using Webserver.Api.Hubs; 4 | using Webserver.Api.Hubs.Clients; 5 | using Webserver.Api.Models; 6 | 7 | namespace Webserver.Api.Controllers 8 | { 9 | [ApiController] 10 | [Route("[controller]")] 11 | public class ChatController : ControllerBase 12 | { 13 | public readonly IHubContext _chatHub; 14 | public ChatController(IHubContext chatHub) 15 | { 16 | _chatHub = chatHub; 17 | } 18 | 19 | [HttpPost("messages")] 20 | public async Task Post(ChatMessage message) 21 | { 22 | // run some logic... 23 | // System.Console.WriteLine(message.Message); 24 | await _chatHub.Clients.All.ReceiveMessage(message); 25 | } 26 | 27 | 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/webserver/Hubs/ChatHub.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR; 2 | using Webserver.Api.Hubs.Clients; 3 | 4 | namespace Webserver.Api.Hubs 5 | { 6 | public class ChatHub : Hub 7 | { } 8 | } -------------------------------------------------------------------------------- /src/webserver/Hubs/Clients/IChatClient.cs: -------------------------------------------------------------------------------- 1 | using Webserver.Api.Models; 2 | 3 | namespace Webserver.Api.Hubs.Clients 4 | { 5 | public interface IChatClient 6 | { 7 | Task ReceiveMessage(ChatMessage message); 8 | } 9 | } -------------------------------------------------------------------------------- /src/webserver/Models/ChatMessage.cs: -------------------------------------------------------------------------------- 1 | namespace Webserver.Api.Models 2 | { 3 | public class ChatMessage 4 | { 5 | public string Activity { get; set; } = string.Empty; 6 | public string Content { get; set; } = string.Empty; 7 | } 8 | } -------------------------------------------------------------------------------- /src/webserver/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.SignalR; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.Extensions.Logging; 10 | using Webserver.Api.Hubs; 11 | using Webserver.Api.Hubs.Clients; 12 | 13 | namespace Webserver.Api 14 | { 15 | public class Program 16 | { 17 | public static IHubContext? hubContext; 18 | public static void Main(string[] args) 19 | { 20 | var host = CreateHostBuilder(args).Build(); 21 | hubContext = host.Services.GetService>(); 22 | host.Run(); 23 | 24 | } 25 | 26 | public static IHostBuilder CreateHostBuilder(string[] args) => 27 | Host.CreateDefaultBuilder(args) 28 | .ConfigureWebHostDefaults(webBuilder => 29 | { 30 | webBuilder.UseStartup(); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/webserver/Startup.cs: -------------------------------------------------------------------------------- 1 | using Webserver.Api.Hubs; 2 | 3 | namespace Webserver.Api; 4 | 5 | public class Startup 6 | { 7 | public Startup(IConfiguration configuration) 8 | { 9 | Configuration = configuration; 10 | } 11 | 12 | public IConfiguration Configuration { get; } 13 | 14 | // This method gets called by the runtime. Use this method to add services to the container. 15 | public void ConfigureServices(IServiceCollection services) 16 | { 17 | services.AddControllers(); 18 | 19 | services.AddSignalR().AddMessagePackProtocol(); 20 | 21 | services.AddCors(options => 22 | { 23 | options.AddPolicy("ClientPermission", pb => 24 | pb.AllowAnyHeader() 25 | .AllowAnyMethod() 26 | .SetIsOriginAllowed(_ => true) 27 | ); 28 | }); 29 | } 30 | 31 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 32 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 33 | { 34 | 35 | if (env.IsDevelopment()) 36 | { 37 | app.UseDeveloperExceptionPage(); 38 | } 39 | 40 | 41 | 42 | app.UseHttpsRedirection(); 43 | 44 | 45 | app.UseRouting(); 46 | app.UseCors("ClientPermission"); 47 | app.UseAuthorization(); 48 | 49 | app.UseEndpoints(endpoints => 50 | { 51 | 52 | endpoints.MapControllers(); 53 | endpoints.MapHub("/hubs/chat"); 54 | 55 | }); 56 | 57 | 58 | } 59 | } -------------------------------------------------------------------------------- /src/webserver/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/webserver/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /src/webserver/webserver.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/webutils/Program.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine; 2 | using backtesting_engine.interfaces; 3 | using Utilities; 4 | 5 | namespace backtesting_engine_web; 6 | 7 | public interface IWebUtils 8 | { 9 | Task Invoke(PriceObj priceObj); 10 | } 11 | 12 | public class WebUtilsMock : IWebUtils 13 | { 14 | public Task Invoke(PriceObj priceObj) 15 | { 16 | return Task.FromResult(0); 17 | } 18 | } 19 | 20 | public class WebUtils : IWebUtils 21 | { 22 | private List ohlcList = new List(); 23 | private OhlcObject lastItem = new OhlcObject(); 24 | private readonly IWebNotification webNotification; 25 | 26 | public WebUtils(IWebNotification webNotification) 27 | { 28 | this.webNotification = webNotification; 29 | } 30 | 31 | public async Task Invoke(PriceObj priceObj) 32 | { 33 | ohlcList = GenericOhlc.CalculateOHLC(priceObj, priceObj.ask, TimeSpan.FromMinutes(60), ohlcList); 34 | 35 | if (ohlcList.Count > 1) 36 | { 37 | var secondLast = ohlcList[ohlcList.Count - 2]; 38 | if (secondLast.complete && secondLast.close != lastItem.close) 39 | { 40 | await webNotification.PriceUpdate(secondLast, true); 41 | lastItem = secondLast; 42 | } 43 | else 44 | { 45 | await webNotification.PriceUpdate(ohlcList.Last()); 46 | } 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/webutils/webutils.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/backtesting/EnvironmentVariableTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using backtesting_engine; 4 | using backtesting_engine.interfaces; 5 | using Moq; 6 | using Moq.Protected; 7 | using Utilities; 8 | using Xunit; 9 | 10 | namespace Tests; 11 | 12 | public class EnvironmentVariableTests 13 | { 14 | [Fact] 15 | public void CheckEnvironmentVariables() 16 | { 17 | // Arrange 18 | var environmentMock = new Mock(); 19 | environmentMock.SetupGet(x=>x.symbols).Returns(new string[]{"TestEnvironmentSetup"}); 20 | 21 | // Assert 22 | Assert.Equal(new string[]{"TestEnvironmentSetup"}, environmentMock.Object.symbols); 23 | } 24 | 25 | [Fact] 26 | public void CheckEnvironmentVariables2() 27 | { 28 | // Arrange 29 | var environmentMock = new Mock(); 30 | 31 | environmentMock.SetupGet(x=>x.loadFromEnvironmnet).Returns(false); 32 | environmentMock.SetupGet(x=>x.scalingFactor).Returns("TestEnvironmentSetup,1;"); 33 | 34 | Dictionary localdictionary = new Dictionary(); 35 | localdictionary.Add("TestEnvironmentSetup", 1m); 36 | 37 | var scalingFactor = environmentMock.Object.GetScalingFactor("TestEnvironmentSetup"); 38 | 39 | // Assert 40 | Assert.Equal(1, scalingFactor); 41 | Assert.Equal(localdictionary, environmentMock.Object.getScalingFactorDictionary()); 42 | 43 | } 44 | 45 | 46 | 47 | 48 | } -------------------------------------------------------------------------------- /tests/backtesting/IngestTests/EnvironmentTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using backtesting_engine; 6 | using backtesting_engine_ingest; 7 | using Moq; 8 | using Utilities; 9 | using Xunit; 10 | 11 | namespace Tests; 12 | 13 | public class IngestEnvironmentTests 14 | { 15 | [Fact] 16 | public void TestFilePath(){ 17 | 18 | var envMock = TestEnvironment.SetEnvironmentVariables(); 19 | var ingestMock = new Mock(envMock.Object){ 20 | CallBase = true 21 | }; 22 | 23 | // Environment.SetEnvironmentVariable("folderPath", PathUtil.GetTestPath("")); 24 | 25 | ingestMock.Object.EnvironmentSetup(); 26 | 27 | Assert.True(ingestMock.Object.fileNames.All(x=>x.Contains(TestEnvironment.folderPath))); 28 | Assert.True(ingestMock.Object.fileNames.Count == TestEnvironment.fileNames.Length); 29 | } 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /tests/backtesting/IngestTests/ObjectsPopulateTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using System.Threading.Tasks.Dataflow; 9 | using backtesting_engine; 10 | using backtesting_engine_ingest; 11 | using Moq; 12 | using Moq.Protected; 13 | using Utilities; 14 | using Xunit; 15 | 16 | namespace Tests; 17 | public class ObjectsPopulateTest 18 | { 19 | 20 | [Theory] 21 | [InlineData("FileName1")] 22 | [InlineData("FileName1", "FileName2")] 23 | [InlineData("FileName1", "FileName2", "FileName3")] 24 | public async void CheckStreamDictionaryContainsAllFiles(params string[] fileNames) 25 | { 26 | // Arrange 27 | var envMock = TestEnvironment.SetEnvironmentVariables(); 28 | 29 | // The Symbols for the filenames need to be defined 30 | envMock.SetupGet(x=>x.symbols).Returns(new string[]{"FileName1","FileName2","FileName3"}); 31 | 32 | var inputMock = new Mock(envMock.Object); 33 | 34 | // stub out the CoordinatedFileRead so the method doesn't try to read from file 35 | inputMock.Protected() 36 | .Setup("LoopStreamDictionaryAndReadLine", ItExpr.IsAny>(), ItExpr.IsAny>()) 37 | .Returns(Task.FromResult(true)); 38 | 39 | // stub out the StreamReader method so the method doesn't try to read from file 40 | inputMock.Protected() 41 | .Setup("ReadFile", ItExpr.IsAny()) 42 | .Returns(StreamReader.Null); 43 | 44 | // stub out the StreamReader Cleanup method so the method doesn't try to dipose of the streamreader 45 | inputMock.Protected() 46 | .Setup("StreamReaderCleanup"); 47 | 48 | // inputMock.Protected().SetupGet>("fileNames").Returns(fileNames); 49 | inputMock.SetupGet(x => x.fileNames).Returns(fileNames.ToList()); 50 | // inputMock.Object.fileNames = fileNames.ToList(); 51 | 52 | // Act 53 | var inputObj = inputMock.Object; 54 | await inputObj.ReadLines(new BufferBlock(), It.IsAny()); 55 | 56 | // Assert 57 | Dictionary streamDic = inputObj.streamDictionary; 58 | Assert.Equal(fileNames.Length, streamDic.Count); 59 | 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /tests/backtesting/IngestTests/ValidationTests.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine_ingest; 2 | using Xunit; 3 | 4 | namespace Tests; 5 | public class IngestValidationTests 6 | { 7 | 8 | [Theory] 9 | [InlineData(true, new string[] { "2018-01-01T22:52:26.862+00:00", "1.20146", "1.20138","1.2","1.3"})] 10 | [InlineData(false, new string[] {})] 11 | [InlineData(false, new string[] { "" })] 12 | [InlineData(false, new string[] { "","","","","" })] 13 | [InlineData(false, new string[] { "a","a","a","a","a" })] 14 | [InlineData(false, new string[] { "a","1.2","a","a","a" })] 15 | public void CheckValidation(bool expectedResult, string[] strings) 16 | { 17 | // Act 18 | var outputResult = Ingest.ArrayHasRightValues(strings); 19 | Assert.Equal(expectedResult, outputResult); 20 | } 21 | 22 | [Theory] 23 | [InlineData(true, "2018-01-01T22:52:26.862+00:00")] 24 | [InlineData(true, "2018-01-01T22:52:26.862")] 25 | [InlineData(false, "2018-01-01 22:52:26.862")] 26 | [InlineData(false, "2018-01-01")] 27 | [InlineData(false, "a")] 28 | public void CheckDateExtract(bool expectedResult, string dateTimeString) 29 | { 30 | // Act 31 | var outputResult = Ingest.extractDt(dateTimeString).parsed; 32 | Assert.Equal(expectedResult, outputResult); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /tests/backtesting/OperationTests/PositionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using backtesting_engine; 8 | using backtesting_engine.analysis; 9 | using backtesting_engine.interfaces; 10 | using backtesting_engine_models; 11 | using backtesting_engine_operations; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using Moq; 14 | using Nest; 15 | using Utilities; 16 | using Xunit; 17 | 18 | namespace Tests; 19 | 20 | public class PositionTests 21 | { 22 | 23 | static string symbolName="TestEnvironmentSetup"; 24 | 25 | public static ServiceProvider Setup(decimal accountEquity) 26 | { 27 | // Arrange Environment Variables 28 | var environmentMock = TestEnvironment.SetEnvironmentVariables(); 29 | 30 | // Explicitly set the trading environment 31 | environmentMock.SetupGet(x=>x.accountEquity).Returns(accountEquity.ToString()); 32 | environmentMock.SetupGet(x=>x.scalingFactor).Returns("TestEnvironmentSetup,1;"); 33 | 34 | var environmentObj = environmentMock.Object; 35 | 36 | // Setup local dependency provider 37 | return new ServiceCollection() 38 | .AddSingleton(environmentObj) 39 | .AddSingleton() 40 | .AddSingleton() 41 | .AddSingleton(new Mock().Object) 42 | .AddSingleton() 43 | .AddSingleton() 44 | .AddSingleton() 45 | .AddSingleton() 46 | .AddSingleton() 47 | .BuildServiceProvider(true); 48 | } 49 | 50 | public static IEnumerable Data => 51 | new List 52 | { 53 | // fields: ask, bid, account equity, direction 54 | new object[] {120m, 150m, 100m, "BUY"}, // positive test 55 | new object[] {100m, 50m, 200m, "BUY"}, // different account opening 56 | new object[] {100m, 20m, 1000m, "BUY"}, // large initial account 57 | new object[] {100m, 200m, 100m, "BUY"}, // negative test 58 | new object[] {100m, 200m, 100m, "SELL"}, // positive test 59 | new object[] {200m, 100m, 100m, "SELL"}, // negative test 60 | }; 61 | 62 | [Theory] 63 | [MemberData(nameof(Data))] 64 | public void BuyCalculationsTest(decimal ask, decimal bid, decimal accountEquity, string direction) { 65 | 66 | TradeDirection tradeDirection = (TradeDirection)Enum.Parse(typeof(TradeDirection), direction); 67 | 68 | var provider = Setup(accountEquity); 69 | var tradingObject = provider.GetService(); 70 | var positions = provider.GetService(); 71 | var envVariables = provider.GetService() ?? new EnvironmentVariables(); 72 | var openOrder = provider.GetService(); 73 | 74 | // Act 75 | // Inital price event 76 | var priceObj = new PriceObj() { 77 | symbol=symbolName, 78 | ask=ask, 79 | bid=bid, 80 | }; 81 | 82 | var key = DictionaryKeyStrings.OpenTrade(priceObj.symbol, priceObj.date); 83 | 84 | // Create a trade request object to open a trade 85 | openOrder?.Request(new RequestObject(priceObj, 86 | tradeDirection, 87 | envVariables, key) { 88 | size = 1, 89 | }); 90 | 91 | // Create a new price event 92 | var priceObjNext = new PriceObj(){ 93 | symbol=symbolName, 94 | bid=bid, 95 | ask=ask 96 | }; 97 | 98 | positions?.Review(priceObjNext); 99 | 100 | // There is now an order excution delay on closing trades 101 | priceObjNext.date=priceObjNext.date.AddSeconds(61); 102 | positions?.PushRequests(priceObjNext); 103 | 104 | // var slippage = 1m / envVariables?.GetScalingFactor(symbolName); 105 | var slippage = 0; 106 | 107 | var expectedPnL = accountEquity - ( (ask - slippage) - bid); 108 | if(tradeDirection == TradeDirection.SELL){ 109 | expectedPnL = ( (bid+slippage) - ask ) + accountEquity; 110 | } 111 | 112 | // Assert 113 | Assert.Equal(1, tradingObject?.tradeHistory.Count); // one record has been added 114 | Assert.Equal(expectedPnL, tradingObject?.accountObj.pnl); // one record has been added 115 | } 116 | 117 | 118 | } -------------------------------------------------------------------------------- /tests/backtesting/Shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | 5 | namespace Tests; 6 | 7 | public static class ReflectionExtensions { 8 | public static T GetFieldValue(this object obj, string name) { 9 | // Set the flags so that private and public fields from instances will be found 10 | var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; 11 | var field = obj.GetType().GetField(name, bindingFlags); 12 | if (field?.GetValue(obj) is T myValue) 13 | return myValue; 14 | 15 | return default!; 16 | } 17 | 18 | } 19 | 20 | public static class PathUtil { 21 | 22 | public static string GetTestPath(string relativePath) 23 | { 24 | var codeBaseUrl = new Uri(Assembly.GetExecutingAssembly().Location); 25 | var codeBasePath = Uri.UnescapeDataString(codeBaseUrl.AbsolutePath); 26 | var dirPath = Path.GetDirectoryName(codeBasePath) ?? ""; 27 | 28 | System.Console.WriteLine(dirPath); 29 | 30 | if(relativePath.Length > 0){ 31 | return Path.Combine(dirPath, "Resources", relativePath); 32 | } else { 33 | return Path.Combine(dirPath, "Resources"); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/backtesting/StrategyTests/RandomStrategyTests.cs: -------------------------------------------------------------------------------- 1 | using backtesting_engine; 2 | using backtesting_engine.interfaces; 3 | using backtesting_engine_models; 4 | using backtesting_engine_operations; 5 | using backtesting_engine_strategies; 6 | using Moq; 7 | using Xunit; 8 | 9 | namespace Tests; 10 | 11 | public class RandomStrategyTests 12 | { 13 | 14 | [Theory(Skip = "Skip for now")] 15 | [InlineData(10,10,1)] 16 | [InlineData(20,20,2)] 17 | public async void TestInvokeMethod(decimal stopDistanceInPips, decimal limitDistanceInPips, decimal tradingSize){ 18 | 19 | var envMock = TestEnvironment.SetEnvironmentVariables(); 20 | envMock.SetupGet(x=>x.stopDistanceInPips).Returns(stopDistanceInPips.ToString()); 21 | envMock.SetupGet(x=>x.limitDistanceInPips).Returns(limitDistanceInPips.ToString()); 22 | envMock.SetupGet(x=>x.tradingSize).Returns(tradingSize.ToString()); 23 | 24 | var requestOpenTradeMock = new Mock(); 25 | var webNotificationMock = new Mock(); 26 | var tradeObjectsMock = new TradingObjects(envMock.Object); 27 | var closeOrderMock = new Mock(); 28 | 29 | var priceObjNext = new PriceObj(){ 30 | symbol="TestEnvironmentSetup", 31 | bid=100, 32 | ask=120 33 | }; 34 | 35 | RequestObject? output = null; 36 | 37 | requestOpenTradeMock.Setup(x=>x.Request(It.IsAny())) 38 | .Callback( ( RequestObject incomingObject) => { 39 | output=incomingObject; 40 | }); 41 | 42 | 43 | var randomStrategyMock = new Mock(requestOpenTradeMock.Object, 44 | tradeObjectsMock, 45 | envMock.Object, 46 | closeOrderMock.Object, 47 | webNotificationMock.Object){ 48 | CallBase = true 49 | }; 50 | 51 | await randomStrategyMock.Object.Invoke(priceObjNext); 52 | priceObjNext.date = priceObjNext.date.AddSeconds(61); 53 | await randomStrategyMock.Object.Invoke(priceObjNext); 54 | 55 | Assert.Equal(priceObjNext.bid, output?.priceObj.bid); 56 | Assert.Equal(tradingSize, output?.size); 57 | Assert.Equal(stopDistanceInPips, output?.stopDistancePips); 58 | Assert.Equal(limitDistanceInPips, output?.limitDistancePips); 59 | Assert.NotEmpty(output?.key); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/backtesting/SystemTests/SystemTests.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.Threading.Tasks; 4 | using backtesting_engine; 5 | using backtesting_engine.analysis; 6 | using backtesting_engine.interfaces; 7 | using backtesting_engine_ingest; 8 | using backtesting_engine_operations; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Moq; 11 | using Nest; 12 | using trading_exception; 13 | using Utilities; 14 | using Xunit; 15 | 16 | namespace Tests; 17 | 18 | public class SystemTests 19 | { 20 | 21 | [Fact] 22 | public void TestTradeException() 23 | { 24 | 25 | var environmentMock = TestEnvironment.SetEnvironmentVariables(); 26 | var environmentObj = environmentMock.Object; 27 | 28 | var provider = new ServiceCollection() 29 | .AddSingleton(environmentObj) 30 | .AddSingleton() 31 | .AddSingleton(new Mock().Object) 32 | .AddSingleton() 33 | .AddSingleton() 34 | .BuildServiceProvider(); 35 | 36 | var reportingMock = new Mock(); 37 | var esMock = new Mock().Object; 38 | 39 | IPositions position = new Mock().Object; 40 | var systemMock = new Mock(provider, reportingMock.Object, esMock, environmentObj, position); 41 | 42 | systemMock.Setup(x => x.StartEngine()) 43 | .Throws(new TradingException(message: "error", "", environmentObj)); 44 | 45 | var output = ""; 46 | reportingMock.Setup(x => x.SendStack(It.IsAny())).Returns(Task.FromResult(true)); 47 | reportingMock.Setup(x => x.EndOfRunReport(It.IsAny())).Callback((string message) => 48 | { 49 | output = message; 50 | }); 51 | 52 | var x = systemMock.Object; // Call System Constructor 53 | 54 | Assert.Equal("error", output); 55 | 56 | } 57 | 58 | [Fact] 59 | public void TestNormalException() 60 | { 61 | 62 | // Arrange the environment mocks 63 | var environmentMock = TestEnvironment.SetEnvironmentVariables(); 64 | var environmentObj = environmentMock.Object; 65 | 66 | // Setup the Service Provider just for the services we need 67 | var provider = new ServiceCollection() 68 | .AddSingleton(environmentObj) 69 | .AddSingleton(new Mock().Object) 70 | .AddSingleton() 71 | .AddSingleton() 72 | .AddSingleton(new Mock().Object) 73 | 74 | .BuildServiceProvider(); 75 | 76 | 77 | // Mock an empty reporting object, we don't want to send any reports 78 | var reportingMock = new Mock(); 79 | var esMock = new Mock().Object; 80 | 81 | IPositions position = new Mock().Object; 82 | var systemMock = new Mock(provider, reportingMock.Object, esMock, environmentObj, position); 83 | systemMock.Setup(x => x.StartEngine()) 84 | .Throws(new ArgumentException(message: "error")); 85 | 86 | var output = ""; 87 | reportingMock.Setup(x => x.SendStack(It.IsAny())).Returns(Task.FromResult(true)); 88 | reportingMock.Setup(x => x.EndOfRunReport(It.IsAny())).Callback((string message) => 89 | { 90 | output = message; 91 | }); 92 | 93 | var x = systemMock.Object; // Call System Constructor 94 | 95 | Assert.Equal("error", output); 96 | 97 | } 98 | 99 | 100 | } -------------------------------------------------------------------------------- /tests/backtesting/TestEnvironmentSetup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Moq; 5 | using Moq.Protected; 6 | using Tests; 7 | using Utilities; 8 | 9 | class TestEnvironment { 10 | 11 | public static string folderPath {get;set;} = PathUtil.GetTestPath("TestEnvironmentSetup"); 12 | public static string[] fileNames {get;set;} = new string[]{"testSymbol.csv"}; 13 | 14 | public static Mock SetEnvironmentVariables(){ 15 | 16 | Environment.SetEnvironmentVariable("consoleLog", "false"); 17 | 18 | var environmentMock = new Mock(); 19 | environmentMock.SetupGet(x=>x.loadFromEnvironmnet).Returns(false); 20 | 21 | environmentMock.SetupGet(x=>x.symbols).Returns(new string[]{"TestEnvironmentSetup"}); 22 | environmentMock.SetupGet(x=>x.scalingFactor).Returns("TestEnvironmentSetup,1000;"); 23 | environmentMock.SetupGet(x=>x.symbolFolder).Returns("Resources"); 24 | environmentMock.SetupGet(x=>x.strategy).Returns("debug"); 25 | environmentMock.SetupGet(x=>x.runID).Returns("debug"); 26 | environmentMock.SetupGet(x=>x.elasticUser).Returns("debug"); 27 | environmentMock.SetupGet(x=>x.elasticPassword).Returns("debug"); 28 | environmentMock.SetupGet(x=>x.tickDataFolder).Returns(Path.Combine(Path.GetFullPath("./" + "Resources"))); 29 | environmentMock.SetupGet(x=>x.elasticCloudID).Returns("my-deployment:bG9jYWxob3N0JDE3OTIwMDJkLTU0YjQ0NTZhYTNhNTg0M2QwZjgxNWM3OSQ1NGI0NDU2YWEzYTU4NDNkMGY4MTVjNzk="); 30 | environmentMock.SetupGet(x=>x.accountEquity).Returns("100"); 31 | environmentMock.SetupGet(x=>x.stopDistanceInPips).Returns("100"); 32 | environmentMock.SetupGet(x=>x.limitDistanceInPips).Returns("10"); 33 | environmentMock.SetupGet(x=>x.maximumDrawndownPercentage).Returns("50"); 34 | environmentMock.SetupGet(x=>x.s3Bucket).Returns("debug"); 35 | environmentMock.SetupGet(x=>x.s3Path).Returns("debug"); 36 | environmentMock.SetupGet(x=>x.years).Returns(new int[]{2006}); 37 | environmentMock.SetupGet(x=>x.reportingEnabled).Returns(false); 38 | environmentMock.SetupGet(x=>x.runIteration).Returns("0"); 39 | environmentMock.SetupGet(x=>x.fasterProcessingBySkippingSomeTickData).Returns(false); 40 | 41 | 42 | return environmentMock; 43 | } 44 | } -------------------------------------------------------------------------------- /tests/backtesting/UtilitesTests/ExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using backtesting_engine; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Utilities; 6 | using Xunit; 7 | 8 | namespace Tests; 9 | 10 | public class ExtensionTests 11 | { 12 | 13 | [Fact] 14 | public void DictionaryKeyStringsTests(){ 15 | 16 | var currentDt = DateTime.Now; 17 | 18 | var priceObj = new PriceObj() { 19 | symbol="symbolName", 20 | date=currentDt 21 | }; 22 | 23 | var output = DictionaryKeyStrings.OpenTrade(priceObj.symbol,priceObj.date); 24 | Assert.Contains(priceObj.symbol + "-" + priceObj.date, output); 25 | } 26 | 27 | [Fact] 28 | public void ServiceExtensionTestsInvalidStrategy(){ 29 | 30 | var envMock = TestEnvironment.SetEnvironmentVariables(); 31 | 32 | // Strategy that doesn't exist 33 | envMock.SetupGet(x=>x.strategy).Returns("doesntExist"); 34 | Func act = () => new ServiceCollection().RegisterStrategies(envMock.Object); 35 | Assert.Throws(act); 36 | } 37 | 38 | [Fact] 39 | public void ServiceExtensionTestsValidStrategy(){ 40 | 41 | var envMock = TestEnvironment.SetEnvironmentVariables(); 42 | 43 | // Strategy that does exist, case sensitive 44 | envMock.SetupGet(x=>x.strategy).Returns("RandomStrategy"); 45 | var collection = new ServiceCollection().RegisterStrategies(envMock.Object); 46 | var response = collection.All(x=> x.ImplementationType!=null && x.ImplementationType.Name == "RandomStrategy"); 47 | Assert.True(response); 48 | 49 | } 50 | } -------------------------------------------------------------------------------- /tests/resources/TestEnvironmentSetup/testSymbol.csv: -------------------------------------------------------------------------------- 1 | 2018-01-01T01:00:00.594+00:00,1.35104,1.35065,1.5,0.75 -------------------------------------------------------------------------------- /tests/resources/testSymbol.csv: -------------------------------------------------------------------------------- 1 | 2018-01-01T01:00:00.594+00:00,1.35104,1.35065,1.5,0.75 -------------------------------------------------------------------------------- /tests/test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | 25 | 26 | 30 | 31 | 32 | 33 | --------------------------------------------------------------------------------