├── .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 |  [](https://sonarcloud.io/summary/overall?id=mccaffers_backtesting-engine) [](https://github.com/mccaffers/backtesting-engine/actions/workflows/build.yml) [](https://sonarcloud.io/summary/new_code?id=mccaffers_backtesting-engine) [](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 | 
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 |
--------------------------------------------------------------------------------
/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