mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
codebase: Remove web frontend and related services (#2067)
* codebase: Remove web frontend and related services * refactor: Update StartPPROF to accept context and adjust related tests * refactor: Simplify SetIfZero functions and update related tests * config: Clarify DowngradeConfig method documentation regarding permanent removal of deprecated fields * refactor: Rename setIfZeroAndWarn to setDefaultIfZeroWarn for clarity and update related calls * refactor: Update error handling in DataHistoryManager and remove redundant error variable
This commit is contained in:
24
.github/workflows/tests.yml
vendored
24
.github/workflows/tests.yml
vendored
@@ -158,27 +158,3 @@ jobs:
|
||||
run: |
|
||||
docker run --env SKIP_WRAPPER_CI_TESTS=true --env CI=true --env GCT_DOCKER_CI=true --rm gct-backend-amd64
|
||||
|
||||
frontend:
|
||||
name: GoCryptoTrader frontend
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel previous workflow runs
|
||||
uses: styfle/cancel-workflow-action@0.12.1
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '10.8.x'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: web/package-lock.json
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cd web/
|
||||
npm install
|
||||
npm run lint
|
||||
npm run build
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,8 +1,8 @@
|
||||
wrapperconfig.json
|
||||
config.json
|
||||
config.dat
|
||||
node_modules
|
||||
lib
|
||||
.DS_STORE
|
||||
|
||||
# VS Code
|
||||
.vscode
|
||||
@@ -37,10 +37,6 @@ backtester/btcli/btcli
|
||||
sqlboiler.toml
|
||||
sqlboiler.json
|
||||
|
||||
# GCT API Check
|
||||
backup.json
|
||||
.DS_STORE
|
||||
|
||||
# Designated generated files dir
|
||||
target/
|
||||
|
||||
|
||||
@@ -73,7 +73,6 @@ However, we welcome pull requests for any exchange which does not match this cri
|
||||
+ Scripting support. See [gctscript](/gctscript/README.md).
|
||||
+ Recent and historic trade processing. See [trades](/exchanges/trade/README.md).
|
||||
+ Backtesting application. An event-driven backtesting tool to test and iterate trading strategies using historical or custom data. See [backtester](/backtester/README.md).
|
||||
+ WebGUI (discontinued).
|
||||
+ Exchange HTTP mock testing. See [mock](/exchanges/mock/README.md).
|
||||
+ Exchange multichain deposits and withdrawals for specific exchanges. See [multichain transfer support](/docs/MULTICHAIN_TRANSFER_SUPPORT.md).
|
||||
|
||||
@@ -134,7 +133,7 @@ go build -tags=sonic_on
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ Creating strategies requires programming skills. [Here](/backtester/eventhandler
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ go run .
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ If any changes were made, ensure that the `rpc.proto` file is formatted correctl
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Common contains some basic data types which are used throughout.
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -288,7 +288,7 @@ See below for a set of tables and fields, expected values and what they can do
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ The config builder will ask you all the necessary questions required to create a
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ Use the provided config builder under `/backtester/config/configbuilder` or modi
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ This can also be used to implement other means to load data for the backtester t
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Trade data represents the raw trading data on an exchange. Every buy or sell act
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ See individual exchange implementations [here](/exchanges) and the interface use
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Additionally, you can view an example under `./testdata/binance_BTCUSDT_24h-trad
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ Database configuration details can be overridden in the `.strat` config file to
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Its incredibly risky to enable `real-orders`. *Past performance is no guarantee
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ A flow of the application is as follows:
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ A flow of the application is as follows:
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ The GRPC server is responsible for handling requests from the client. All GRPC f
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ A flow of the application is as follows:
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Below is an overview of how event handlers are used
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ It is used by `backtest.Backtester` and it accepts appending any struct which im
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ The following steps are taken for the `ExecuteOrder` function:
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Slippage is calculated in two ways in the GoCryptoTrader Backtester
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ The following steps are taken for the `Update` function:
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ The compliance manager is used to store all events at each time interval. When d
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Every data event will update and calculate holdings value based on the new price
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ See config package [readme](/backtester/config/README.md) to view the risk relat
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ The sizing package ensures that all potential orders raised are within both the
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ If the strategy config setting `DisableUSDTracking` is `false`, then the GoCrypt
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ Each strategy has a unique name and is to be added to the function `getStrategie
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ The strategy base file has basic implementations of the `strategies.Handler` int
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ This strategy does support strategy customisation in the following ways:
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ This strategy does not support customisation
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ This strategy does support strategy customisation in the following ways:
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ This strategy does support strategy customisation in the following ways:
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Below is an overview of how events are used
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ The event type is an important base for all other events. It allows for consiste
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ SetAmount(float64)
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ The Kline event type is used to store the candle data of an individual data even
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ The Order Event Type is based on `common.EventHandler` and `common.Directioner`
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ The signal event will contain data such as price, the direction as well as the r
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ No. The already existing `CurrencySettings` will populate the funding manager wi
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ This is currently not supported. If this is a feature you would like to have, pl
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ See the following for instructions on installing Golang in WSL: [here](https://a
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ Upon startup, the GoCryptoTrader Backtester will load the strategy and run it fo
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ Upon startup, the GoCryptoTrader Backtester will load the strategy and run it fo
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Output example:
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<title>{{.Config.Nickname}} Results</title>
|
||||
<!-- Font Awesome -->
|
||||
<link rel="icon" href="https://raw.githubusercontent.com/thrasher-corp/gocryptotrader/a1a667bab9150e611dc04bad43fa49457171936a/web/src/assets/images/gctlogo-notext.svg" />
|
||||
<link rel="icon" href="https://raw.githubusercontent.com/thrasher-corp/gocryptotrader/master/docs/assets/gctlogo-notext.svg" />
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" rel="stylesheet"/>
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet"/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GoCryptoTrader dbseed tool
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
<img src="/docs/assets/page-logo.png" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
@@ -103,7 +103,7 @@ btc markets,
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ upper := strings.ToUpper(testString)
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
{{define "engine apiserver" -}}
|
||||
{{template "header" .}}
|
||||
## Current Features for {{.CapitalName}}
|
||||
+ The API server subsystem is a deprecated service used to host a REST or websocket server to interact with some functions of GoCryptoTrader
|
||||
+ This subsystem is no longer maintained and it is highly encouraged to interact with GRPC endpoints directly where possible
|
||||
+ In order to modify the behaviour of the API server subsystem, you can edit the following inside your config file:
|
||||
|
||||
### deprecatedRPC
|
||||
|
||||
| Config | Description | Example |
|
||||
| ------ | ----------- | ------- |
|
||||
| enabled | If enabled will create a REST server which will listen to commands on the listen address | `true` |
|
||||
| listenAddress | If enabled will listen for REST requests on this address and return a JSON response | `localhost:9050` |
|
||||
|
||||
### websocketRPC
|
||||
|
||||
| Config | Description | Example |
|
||||
| ------ | ----------- | ------- |
|
||||
| enabled | If enabled will create a REST server which will listen to commands on the listen address | `true` |
|
||||
| listenAddress | If enabled will listen for requests on this address and return a JSON response | `localhost:9051` |
|
||||
| connectionLimit | Defines how many connections the websocket RPC server can handle simultanesoly | `1` |
|
||||
| maxAuthFailures | For authenticated endpoints, the amount of failed attempts allowed before disconnection | `3` |
|
||||
| allowInsecureOrigin | Allows use of insecure connections | `true` |
|
||||
|
||||
{{template "donations" .}}
|
||||
{{end}}
|
||||
@@ -74,7 +74,6 @@ However, we welcome pull requests for any exchange which does not match this cri
|
||||
+ Scripting support. See [gctscript](/gctscript/README.md).
|
||||
+ Recent and historic trade processing. See [trades](/exchanges/trade/README.md).
|
||||
+ Backtesting application. An event-driven backtesting tool to test and iterate trading strategies using historical or custom data. See [backtester](/backtester/README.md).
|
||||
+ WebGUI (discontinued).
|
||||
+ Exchange HTTP mock testing. See [mock](/exchanges/mock/README.md).
|
||||
+ Exchange multichain deposits and withdrawals for specific exchanges. See [multichain transfer support](/docs/MULTICHAIN_TRANSFER_SUPPORT.md).
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "donations" -}}
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GoCryptoTrader gRPC client
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
<img src="/docs/assets/page-logo.png" width="350px" height="350px" hspace="70">
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
gws "github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/encoding/json"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
)
|
||||
|
||||
// Vars for the websocket client
|
||||
var (
|
||||
WSConn *gws.Conn
|
||||
)
|
||||
|
||||
// WebsocketEvent is the struct used for websocket events
|
||||
type WebsocketEvent struct {
|
||||
Exchange string `json:"exchange,omitempty"`
|
||||
AssetType string `json:"assetType,omitempty"`
|
||||
Event string
|
||||
Data any
|
||||
}
|
||||
|
||||
// WebsocketAuth is the struct used for a websocket auth request
|
||||
type WebsocketAuth struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// WebsocketEventResponse is the struct used for websocket event responses
|
||||
type WebsocketEventResponse struct {
|
||||
Event string `json:"event"`
|
||||
Data any `json:"data"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// WebsocketOrderbookTickerRequest is a struct used for ticker and orderbook
|
||||
// requests
|
||||
type WebsocketOrderbookTickerRequest struct {
|
||||
Exchange string `json:"exchangeName"`
|
||||
Currency string `json:"currency"`
|
||||
AssetType asset.Item `json:"assetType"`
|
||||
}
|
||||
|
||||
// SendWebsocketEvent sends a websocket event message
|
||||
func SendWebsocketEvent(event string, reqData any, result *WebsocketEventResponse) error {
|
||||
req := WebsocketEvent{
|
||||
Event: event,
|
||||
}
|
||||
|
||||
if reqData != nil {
|
||||
req.Data = reqData
|
||||
}
|
||||
|
||||
err := WSConn.WriteJSON(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = WSConn.ReadJSON(&result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.Error != "" {
|
||||
return errors.New(result.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config.GetConfig()
|
||||
err := cfg.LoadConfig(config.File, true)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load config file: %s", err)
|
||||
}
|
||||
|
||||
listenAddr := cfg.RemoteControl.WebsocketRPC.ListenAddress
|
||||
wsHost := fmt.Sprintf("ws://%s/ws", net.JoinHostPort(common.ExtractHostOrDefault(listenAddr),
|
||||
strconv.Itoa(common.ExtractPortOrDefault(listenAddr))))
|
||||
log.Printf("Connecting to websocket host: %s", wsHost)
|
||||
|
||||
var dialer gws.Dialer
|
||||
var resp *http.Response
|
||||
WSConn, resp, err = dialer.Dial(wsHost, http.Header{})
|
||||
if err != nil {
|
||||
log.Println("Unable to connect to websocket server")
|
||||
return
|
||||
}
|
||||
resp.Body.Close()
|
||||
log.Println("Connected to websocket!")
|
||||
|
||||
log.Println("Authenticating..")
|
||||
shasum := sha256.Sum256([]byte(cfg.RemoteControl.Password))
|
||||
reqData := WebsocketAuth{
|
||||
Username: cfg.RemoteControl.Username,
|
||||
Password: hex.EncodeToString(shasum[:]),
|
||||
}
|
||||
var wsResp WebsocketEventResponse
|
||||
err = SendWebsocketEvent("auth", reqData, &wsResp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Authenticated successfully")
|
||||
|
||||
log.Println("Getting config..")
|
||||
err = SendWebsocketEvent("GetConfig", nil, &wsResp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Fetched config.")
|
||||
|
||||
dataJSON, err := json.Marshal(&wsResp.Data)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var resultCfg config.Config
|
||||
err = json.Unmarshal(dataJSON, &resultCfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Saving config..")
|
||||
origBotName := resultCfg.Name
|
||||
resultCfg.Name = "TEST"
|
||||
err = SendWebsocketEvent("SaveConfig", resultCfg, &wsResp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Saved config!")
|
||||
resultCfg.Name = origBotName
|
||||
err = SendWebsocketEvent("SaveConfig", resultCfg, &wsResp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Saved config (restored original bot name)!")
|
||||
|
||||
log.Println("Getting account info..")
|
||||
err = SendWebsocketEvent("GetAccountInfo", nil, &wsResp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Got account info!")
|
||||
|
||||
log.Println("Getting tickers..")
|
||||
err = SendWebsocketEvent("GetTickers", nil, &wsResp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Got tickers!")
|
||||
|
||||
log.Println("Getting specific ticker..")
|
||||
dataReq := WebsocketOrderbookTickerRequest{
|
||||
Exchange: "Bitfinex",
|
||||
Currency: "BTCUSD",
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
|
||||
err = SendWebsocketEvent("GetTicker", dataReq, &wsResp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Got ticker!")
|
||||
|
||||
log.Println("Getting orderbooks..")
|
||||
err = SendWebsocketEvent("GetOrderbooks", nil, &wsResp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Got orderbooks!")
|
||||
|
||||
log.Println("Getting specific orderbook..")
|
||||
err = SendWebsocketEvent("GetOrderbook", dataReq, &wsResp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Got orderbook!")
|
||||
|
||||
for {
|
||||
var wsEvent WebsocketEvent
|
||||
err = WSConn.ReadJSON(&wsEvent)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Recv'd: %s", wsEvent.Event)
|
||||
}
|
||||
WSConn.Close()
|
||||
}
|
||||
@@ -36,7 +36,7 @@ upper := strings.ToUpper(testString)
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
4
common/cache/README.md
vendored
4
common/cache/README.md
vendored
@@ -1,6 +1,6 @@
|
||||
# GoCryptoTrader package cache
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
<img src="/docs/assets/page-logo.png" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
@@ -48,7 +48,7 @@ func main() {
|
||||
```
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -683,3 +683,13 @@ func (c *Counter) IncrementAndGet() int64 {
|
||||
}
|
||||
return newID
|
||||
}
|
||||
|
||||
// SetIfZero sets the value of p to def if p is the zero value for its type and returns true if it was set
|
||||
func SetIfZero[T comparable](p *T, def T) bool {
|
||||
var zero T
|
||||
if *p != zero {
|
||||
return false
|
||||
}
|
||||
*p = def
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -680,3 +680,15 @@ func TestNilGuard(t *testing.T) {
|
||||
err = NilGuard()
|
||||
require.NoError(t, err, "NilGuard with no arguments must not error")
|
||||
}
|
||||
|
||||
func TestSetIfZero(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := "hello"
|
||||
changed := SetIfZero(&s, "world")
|
||||
assert.False(t, changed, "SetIfZero should not change a non-zero value")
|
||||
assert.Equal(t, "hello", s, "SetIfZero should not change a non-zero value")
|
||||
s = ""
|
||||
changed = SetIfZero(&s, "world")
|
||||
assert.True(t, changed, "SetIfZero should change a zero value")
|
||||
assert.Equal(t, "world", s, "SetIfZero should change a zero value")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GoCryptoTrader package Comms
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
<img src="/docs/assets/page-logo.png" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
@@ -39,7 +39,7 @@ to be exported out to a defined communication medium
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ via Slack:
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ err := s.Connect
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ err := s.Connect
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ via Telegram:
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -263,7 +263,7 @@ servers are configured by the pool array and attempted first to last allowedDiff
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -1580,46 +1579,25 @@ func (c *Config) Save(writerProvider func() (io.Writer, error)) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// CheckRemoteControlConfig checks to see if the old c.Webserver field is used
|
||||
// and migrates the existing settings to the new RemoteControl struct
|
||||
func setDefaultIfZeroWarn[T comparable](scope, name string, p *T, def T) {
|
||||
if common.SetIfZero(p, def) {
|
||||
log.Warnf(log.ConfigMgr, "%s field %q not set, defaulting to `%v`", scope, name, def)
|
||||
}
|
||||
}
|
||||
|
||||
// CheckRemoteControlConfig checks and sets default values for the remote control config
|
||||
func (c *Config) CheckRemoteControlConfig() {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if c.Webserver != nil {
|
||||
port := common.ExtractPortOrDefault(c.Webserver.ListenAddress)
|
||||
host := common.ExtractHostOrDefault(c.Webserver.ListenAddress)
|
||||
setDefaultIfZeroWarn("Remote control", "username", &c.RemoteControl.Username, DefaultGRPCUsername)
|
||||
setDefaultIfZeroWarn("Remote control", "password", &c.RemoteControl.Password, DefaultGRPCPassword)
|
||||
setDefaultIfZeroWarn("Remote control gRPC", "listen address", &c.RemoteControl.GRPC.ListenAddress, "localhost:9052")
|
||||
setDefaultIfZeroWarn("Remote control gRPC", "gRPC proxy listen address", &c.RemoteControl.GRPC.GRPCProxyListenAddress, "localhost:9053")
|
||||
|
||||
c.RemoteControl = RemoteControlConfig{
|
||||
Username: c.Webserver.AdminUsername,
|
||||
Password: c.Webserver.AdminPassword,
|
||||
|
||||
DeprecatedRPC: DepcrecatedRPCConfig{
|
||||
Enabled: c.Webserver.Enabled,
|
||||
ListenAddress: host + ":" + strconv.Itoa(port),
|
||||
},
|
||||
}
|
||||
|
||||
port++
|
||||
c.RemoteControl.WebsocketRPC = WebsocketRPCConfig{
|
||||
Enabled: c.Webserver.Enabled,
|
||||
ListenAddress: host + ":" + strconv.Itoa(port),
|
||||
ConnectionLimit: c.Webserver.WebsocketConnectionLimit,
|
||||
MaxAuthFailures: c.Webserver.WebsocketMaxAuthFailures,
|
||||
AllowInsecureOrigin: c.Webserver.WebsocketAllowInsecureOrigin,
|
||||
}
|
||||
|
||||
port++
|
||||
gRPCProxyPort := port + 1
|
||||
c.RemoteControl.GRPC = GRPCConfig{
|
||||
Enabled: c.Webserver.Enabled,
|
||||
ListenAddress: host + ":" + strconv.Itoa(port),
|
||||
GRPCProxyEnabled: c.Webserver.Enabled,
|
||||
GRPCProxyListenAddress: host + ":" + strconv.Itoa(gRPCProxyPort),
|
||||
}
|
||||
|
||||
// Then flush the old webserver settings
|
||||
c.Webserver = nil
|
||||
if c.RemoteControl.GRPC.GRPCProxyEnabled && !c.RemoteControl.GRPC.Enabled {
|
||||
log.Warnln(log.ConfigMgr, "gRPC proxy cannot be enabled when gRPC is disabled, disabling gRPC proxy")
|
||||
c.RemoteControl.GRPC.GRPCProxyEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1688,7 +1666,6 @@ func (c *Config) UpdateConfig(configPath string, newCfg *Config, dryrun bool) er
|
||||
c.GlobalHTTPTimeout = newCfg.GlobalHTTPTimeout
|
||||
c.Portfolio = newCfg.Portfolio
|
||||
c.Communications = newCfg.Communications
|
||||
c.Webserver = newCfg.Webserver
|
||||
c.Exchanges = newCfg.Exchanges
|
||||
|
||||
if !dryrun {
|
||||
|
||||
@@ -1564,40 +1564,23 @@ func TestGetFilePath(t *testing.T) {
|
||||
|
||||
func TestCheckRemoteControlConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var c Config
|
||||
c.Webserver = &WebserverConfig{
|
||||
Enabled: true,
|
||||
AdminUsername: "satoshi",
|
||||
AdminPassword: "ultrasecurepassword",
|
||||
ListenAddress: ":9050",
|
||||
WebsocketConnectionLimit: 5,
|
||||
WebsocketMaxAuthFailures: 10,
|
||||
WebsocketAllowInsecureOrigin: true,
|
||||
}
|
||||
|
||||
c.RemoteControl = RemoteControlConfig{}
|
||||
c.CheckRemoteControlConfig()
|
||||
|
||||
if c.RemoteControl.Username != "satoshi" ||
|
||||
c.RemoteControl.Password != "ultrasecurepassword" ||
|
||||
!c.RemoteControl.GRPC.Enabled ||
|
||||
c.RemoteControl.GRPC.ListenAddress != "localhost:9052" ||
|
||||
!c.RemoteControl.GRPC.GRPCProxyEnabled ||
|
||||
c.RemoteControl.GRPC.GRPCProxyListenAddress != "localhost:9053" ||
|
||||
!c.RemoteControl.DeprecatedRPC.Enabled ||
|
||||
c.RemoteControl.DeprecatedRPC.ListenAddress != "localhost:9050" ||
|
||||
!c.RemoteControl.WebsocketRPC.Enabled ||
|
||||
c.RemoteControl.WebsocketRPC.ListenAddress != "localhost:9051" ||
|
||||
!c.RemoteControl.WebsocketRPC.AllowInsecureOrigin ||
|
||||
c.RemoteControl.WebsocketRPC.ConnectionLimit != 5 ||
|
||||
c.RemoteControl.WebsocketRPC.MaxAuthFailures != 10 {
|
||||
t.Error("unexpected results")
|
||||
}
|
||||
|
||||
// Now test to ensure the previous settings are flushed
|
||||
if c.Webserver != nil {
|
||||
t.Error("old webserver settings should be nil")
|
||||
}
|
||||
assert.Equal(t, "admin", c.RemoteControl.Username, "Username default should be set correctly")
|
||||
assert.Equal(t, "Password", c.RemoteControl.Password, "Password default should be set correctly")
|
||||
assert.Equal(t, "localhost:9052", c.RemoteControl.GRPC.ListenAddress, "ListenAddress default should be set correctly")
|
||||
assert.Equal(t, "localhost:9053", c.RemoteControl.GRPC.GRPCProxyListenAddress, "GRPCProxyListenAddress default should be set correctly")
|
||||
assert.False(t, c.RemoteControl.GRPC.Enabled, "gRPC default should be set correctly")
|
||||
assert.False(t, c.RemoteControl.GRPC.GRPCProxyEnabled, "gRPCProxyEnabled default should be set correctly")
|
||||
c.RemoteControl.GRPC.GRPCProxyEnabled = true
|
||||
c.CheckRemoteControlConfig()
|
||||
assert.False(t, c.RemoteControl.GRPC.GRPCProxyEnabled, "gRPCProxyEnabled should be set to false when gRPC is not enabled")
|
||||
c.RemoteControl.GRPC.Enabled = true
|
||||
c.RemoteControl.GRPC.GRPCProxyEnabled = true
|
||||
c.CheckRemoteControlConfig()
|
||||
assert.True(t, c.RemoteControl.GRPC.Enabled, "gRPC should be true")
|
||||
assert.True(t, c.RemoteControl.GRPC.GRPCProxyEnabled, "gRPCProxyEnabled should be true when gRPC is enabled")
|
||||
}
|
||||
|
||||
func TestCheckConfig(t *testing.T) {
|
||||
|
||||
@@ -69,6 +69,8 @@ const (
|
||||
DefaultUnsetAPIKey = "Key"
|
||||
DefaultUnsetAPISecret = "Secret"
|
||||
DefaultUnsetAccountPlan = "accountPlan"
|
||||
DefaultGRPCUsername = "admin"
|
||||
DefaultGRPCPassword = "Password"
|
||||
)
|
||||
|
||||
// Public errors exported by this package
|
||||
@@ -111,7 +113,6 @@ type Config struct {
|
||||
BankAccounts []banking.Account `json:"bankAccounts"`
|
||||
|
||||
// Deprecated config settings, will be removed at a future date
|
||||
Webserver *WebserverConfig `json:"webserver,omitempty"`
|
||||
CurrencyPairFormat *currency.PairFormat `json:"currencyPairFormat,omitempty"`
|
||||
FiatDisplayCurrency *currency.Code `json:"fiatDispayCurrency,omitempty"`
|
||||
Cryptocurrencies *currency.Currencies `json:"cryptocurrencies,omitempty"`
|
||||
@@ -218,6 +219,8 @@ type Exchange struct {
|
||||
type Profiler struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
MutexProfileFraction int `json:"mutex_profile_fraction"`
|
||||
ListenAddress string `json:"listen_address"`
|
||||
BlockProfileRate int `json:"block_profile_rate"`
|
||||
}
|
||||
|
||||
// NTPClientConfig defines a network time protocol configuration to allow for
|
||||
@@ -239,40 +242,11 @@ type GRPCConfig struct {
|
||||
TimeInNanoSeconds bool `json:"timeInNanoSeconds"`
|
||||
}
|
||||
|
||||
// DepcrecatedRPCConfig stores the deprecatedRPCConfig settings
|
||||
type DepcrecatedRPCConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ListenAddress string `json:"listenAddress"`
|
||||
}
|
||||
|
||||
// WebsocketRPCConfig stores the websocket config info
|
||||
type WebsocketRPCConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ListenAddress string `json:"listenAddress"`
|
||||
ConnectionLimit int `json:"connectionLimit"`
|
||||
MaxAuthFailures int `json:"maxAuthFailures"`
|
||||
AllowInsecureOrigin bool `json:"allowInsecureOrigin"`
|
||||
}
|
||||
|
||||
// RemoteControlConfig stores the RPC services config
|
||||
type RemoteControlConfig struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
|
||||
GRPC GRPCConfig `json:"gRPC"`
|
||||
DeprecatedRPC DepcrecatedRPCConfig `json:"deprecatedRPC"`
|
||||
WebsocketRPC WebsocketRPCConfig `json:"websocketRPC"`
|
||||
}
|
||||
|
||||
// WebserverConfig stores the old webserver config
|
||||
type WebserverConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
AdminUsername string `json:"adminUsername"`
|
||||
AdminPassword string `json:"adminPassword"`
|
||||
ListenAddress string `json:"listenAddress"`
|
||||
WebsocketConnectionLimit int `json:"websocketConnectionLimit"`
|
||||
WebsocketMaxAuthFailures int `json:"websocketMaxAuthFailures"`
|
||||
WebsocketAllowInsecureOrigin bool `json:"websocketAllowInsecureOrigin"`
|
||||
}
|
||||
|
||||
// Post holds the bot configuration data
|
||||
|
||||
@@ -3,6 +3,7 @@ package versions
|
||||
import (
|
||||
v0 "github.com/thrasher-corp/gocryptotrader/config/versions/v0"
|
||||
v1 "github.com/thrasher-corp/gocryptotrader/config/versions/v1"
|
||||
v10 "github.com/thrasher-corp/gocryptotrader/config/versions/v10"
|
||||
v2 "github.com/thrasher-corp/gocryptotrader/config/versions/v2"
|
||||
v3 "github.com/thrasher-corp/gocryptotrader/config/versions/v3"
|
||||
v4 "github.com/thrasher-corp/gocryptotrader/config/versions/v4"
|
||||
@@ -24,4 +25,5 @@ func init() {
|
||||
Manager.registerVersion(7, &v7.Version{})
|
||||
Manager.registerVersion(8, &v8.Version{})
|
||||
Manager.registerVersion(9, &v9.Version{})
|
||||
Manager.registerVersion(10, &v10.Version{})
|
||||
}
|
||||
|
||||
22
config/versions/v10/v10.go
Normal file
22
config/versions/v10/v10.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package v10
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/buger/jsonparser"
|
||||
)
|
||||
|
||||
// Version implements ConfigVersion
|
||||
type Version struct{}
|
||||
|
||||
// UpgradeConfig checks and removes the deprecatedRPC and websocketRPC fields from the remoteControl config
|
||||
func (*Version) UpgradeConfig(_ context.Context, e []byte) ([]byte, error) {
|
||||
e = jsonparser.Delete(e, "remoteControl", "deprecatedRPC")
|
||||
e = jsonparser.Delete(e, "remoteControl", "websocketRPC")
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// DowngradeConfig is a no-op. It does not restore deprecatedRPC or websocketRPC on downgrade as their removal is permanent
|
||||
func (*Version) DowngradeConfig(_ context.Context, e []byte) ([]byte, error) {
|
||||
return e, nil
|
||||
}
|
||||
27
config/versions/v10/v10_test.go
Normal file
27
config/versions/v10/v10_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package v10_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
v10 "github.com/thrasher-corp/gocryptotrader/config/versions/v10"
|
||||
)
|
||||
|
||||
func TestUpgradeConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := []byte(`{"remoteControl":{"enabled":true,"deprecatedRPC":{"enabled":true,"listenAddress":"localhost:9050"},"websocketRPC":{"enabled":true,"listenAddress":"localhost:9051","connectionLimit":1,"maxAuthFailures":3,"allowInsecureOrigin":true}}}`)
|
||||
out, err := new(v10.Version).UpgradeConfig(t.Context(), in)
|
||||
require.NoError(t, err)
|
||||
const expected = `{"remoteControl":{"enabled":true}}`
|
||||
assert.JSONEq(t, expected, string(out))
|
||||
}
|
||||
|
||||
func TestDowngradeConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := []byte("meow, moocow, woof, quack")
|
||||
out, err := new(v10.Version).DowngradeConfig(t.Context(), bytes.Clone(in))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, out, in)
|
||||
}
|
||||
@@ -72,7 +72,9 @@
|
||||
},
|
||||
"profiler": {
|
||||
"enabled": false,
|
||||
"mutex_profile_fraction": 0
|
||||
"mutex_profile_fraction": 0,
|
||||
"listen_address": "localhost:8085",
|
||||
"block_profile_rate": 0
|
||||
},
|
||||
"ntpclient": {
|
||||
"enabled": 0,
|
||||
@@ -207,17 +209,6 @@
|
||||
"grpcProxyEnabled": false,
|
||||
"grpcProxyListenAddress": "localhost:9053",
|
||||
"timeInNanoSeconds": false
|
||||
},
|
||||
"deprecatedRPC": {
|
||||
"enabled": true,
|
||||
"listenAddress": "localhost:9050"
|
||||
},
|
||||
"websocketRPC": {
|
||||
"enabled": true,
|
||||
"listenAddress": "localhost:9051",
|
||||
"connectionLimit": 1,
|
||||
"maxAuthFailures": 3,
|
||||
"allowInsecureOrigin": true
|
||||
}
|
||||
},
|
||||
"portfolioAddresses": {
|
||||
|
||||
@@ -28,7 +28,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ providers.
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ mapstringfloat, err := c.GetRates("USD", "EUR,CHY")
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ mapstringfloat, err := c.GetRates("USD", "EUR,CHY")
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ mapstringfloat, err := c.GetRates("USD", "EUR,CHY")
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ mapstringfloat, err := c.GetRates("USD", "EUR,CHY")
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ mapstringfloat, err := c.GetRates("USD", "EUR,CHY")
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GoCryptoTrader package Database
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
<img src="/docs/assets/page-logo.png" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
@@ -152,7 +152,7 @@ A helper tool [cmd/dbseed](../cmd/dbseed/README.md) has been created for assisti
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
|
||||
web:
|
||||
build: ./web
|
||||
depends_on:
|
||||
- daemon
|
||||
ports:
|
||||
- "9054:80"
|
||||
|
||||
daemon:
|
||||
build: .
|
||||
ports:
|
||||
- "9050:9050"
|
||||
- "9051:9051"
|
||||
- "9052:9052"
|
||||
- "9053:9053"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GoCryptoTrader ADD NEW EXCHANGE
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70" alt="GoCryptoTrader project logo">
|
||||
<img src="/docs/assets/page-logo.png" width="350px" height="350px" hspace="70" alt="GoCryptoTrader project logo">
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GoCryptoTrader Unified API
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
<img src="/docs/assets/page-logo.png" width="350px" height="350px" hspace="70">
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GoCryptoTrader File Hierarchy
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
<img src="/docs/assets/page-logo.png" width="350px" height="350px" hspace="70">
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GoCryptoTrader OHLCV support
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
<img src="/docs/assets/page-logo.png" width="350px" height="350px" hspace="70">
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GoCryptoTrader Documentation
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
<img src="/docs/assets/page-logo.png" width="350px" height="350px" hspace="70">
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
|
||||
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
@@ -1,918 +0,0 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
gws "github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/encoding/json"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
// setupAPIServerManager checks and creates an api server manager
|
||||
func setupAPIServerManager(remoteConfig *config.RemoteControlConfig, pprofConfig *config.Profiler, exchangeManager iExchangeManager, bot iBot, portfolioManager iPortfolioManager, configPath string) (*apiServerManager, error) {
|
||||
if remoteConfig == nil {
|
||||
return nil, errNilRemoteConfig
|
||||
}
|
||||
if pprofConfig == nil {
|
||||
return nil, errNilPProfConfig
|
||||
}
|
||||
if exchangeManager == nil {
|
||||
return nil, errNilExchangeManager
|
||||
}
|
||||
if bot == nil {
|
||||
return nil, errNilBot
|
||||
}
|
||||
if configPath == "" {
|
||||
return nil, errEmptyConfigPath
|
||||
}
|
||||
return &apiServerManager{
|
||||
remoteConfig: remoteConfig,
|
||||
pprofConfig: pprofConfig,
|
||||
restListenAddress: remoteConfig.DeprecatedRPC.ListenAddress,
|
||||
websocketListenAddress: remoteConfig.WebsocketRPC.ListenAddress,
|
||||
exchangeManager: exchangeManager,
|
||||
bot: bot,
|
||||
gctConfigPath: configPath,
|
||||
portfolioManager: portfolioManager,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsRESTServerRunning safely checks whether the subsystem is running
|
||||
func (m *apiServerManager) IsRESTServerRunning() bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
return atomic.LoadInt32(&m.restStarted) == 1
|
||||
}
|
||||
|
||||
// IsWebsocketServerRunning safely checks whether the subsystem is running
|
||||
func (m *apiServerManager) IsWebsocketServerRunning() bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
return atomic.LoadInt32(&m.websocketStarted) == 1
|
||||
}
|
||||
|
||||
// StopRESTServer attempts to shutdown the subsystem
|
||||
func (m *apiServerManager) StopRESTServer() error {
|
||||
if m == nil {
|
||||
return fmt.Errorf("api server %w", ErrNilSubsystem)
|
||||
}
|
||||
if !atomic.CompareAndSwapInt32(&m.restStarted, 1, 0) {
|
||||
return fmt.Errorf("apiserver deprecated server %w", ErrSubSystemNotStarted)
|
||||
}
|
||||
err := m.restHTTPServer.Shutdown(context.Background())
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
m.wgRest.Wait()
|
||||
m.restRouter = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *apiServerManager) StopWebsocketServer() error {
|
||||
if m == nil {
|
||||
return fmt.Errorf("api server %w", ErrNilSubsystem)
|
||||
}
|
||||
if !atomic.CompareAndSwapInt32(&m.websocketStarted, 1, 0) {
|
||||
return fmt.Errorf("apiserver websocket server %w", ErrSubSystemNotStarted)
|
||||
}
|
||||
|
||||
err := m.websocketHTTPServer.Shutdown(context.Background())
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
m.websocketRouter = nil
|
||||
m.websocketHub = nil
|
||||
m.wgWebsocket.Wait()
|
||||
m.websocketHTTPServer = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// newRouter takes in the exchange interfaces and returns a new multiplexer
|
||||
// router
|
||||
func (m *apiServerManager) newRouter(isREST bool) *mux.Router {
|
||||
router := mux.NewRouter().StrictSlash(true)
|
||||
var routes []Route
|
||||
if common.ExtractPortOrDefault(m.websocketListenAddress) == 80 {
|
||||
m.websocketListenAddress = common.ExtractHostOrDefault(m.websocketListenAddress)
|
||||
} else {
|
||||
m.websocketListenAddress = common.ExtractHostOrDefault(m.websocketListenAddress) + ":" +
|
||||
strconv.Itoa(common.ExtractPortOrDefault(m.websocketListenAddress))
|
||||
}
|
||||
|
||||
if isREST {
|
||||
routes = []Route{
|
||||
{"", http.MethodGet, "/", m.getIndex},
|
||||
{"GetAllSettings", http.MethodGet, "/config/all", m.restGetAllSettings},
|
||||
{"SaveAllSettings", http.MethodPost, "/config/all/save", m.restSaveAllSettings},
|
||||
{"AllEnabledAccountInfo", http.MethodGet, "/exchanges/enabled/accounts/all", m.restGetAllEnabledAccountInfo},
|
||||
{"AllActiveExchangesAndCurrencies", http.MethodGet, "/exchanges/enabled/latest/all", m.restGetAllActiveTickers},
|
||||
{"GetPortfolio", http.MethodGet, "/portfolio/all", m.restGetPortfolio},
|
||||
{"AllActiveExchangesAndOrderbooks", http.MethodGet, "/exchanges/orderbook/latest/all", m.restGetAllActiveOrderbooks},
|
||||
}
|
||||
|
||||
if m.pprofConfig.Enabled {
|
||||
if m.pprofConfig.MutexProfileFraction > 0 {
|
||||
runtime.SetMutexProfileFraction(m.pprofConfig.MutexProfileFraction)
|
||||
}
|
||||
log.Debugf(log.RESTSys,
|
||||
"HTTP Go performance profiler (pprof) endpoint enabled: http://%s:%d/debug/pprof/\n",
|
||||
common.ExtractHostOrDefault(m.websocketListenAddress),
|
||||
common.ExtractPortOrDefault(m.websocketListenAddress),
|
||||
)
|
||||
router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
||||
}
|
||||
} else {
|
||||
routes = []Route{
|
||||
{"ws", http.MethodGet, "/ws", m.WebsocketClientHandler},
|
||||
}
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
router.
|
||||
Methods(route.Method).
|
||||
Path(route.Pattern).
|
||||
Name(route.Name).
|
||||
Handler(restLogger(route.HandlerFunc, route.Name)).
|
||||
Host(m.websocketListenAddress)
|
||||
}
|
||||
return router
|
||||
}
|
||||
|
||||
// StartRESTServer starts a REST handler
|
||||
func (m *apiServerManager) StartRESTServer() error {
|
||||
if !atomic.CompareAndSwapInt32(&m.restStarted, 0, 1) {
|
||||
return fmt.Errorf("rest server %w", errAlreadyRunning)
|
||||
}
|
||||
if !m.remoteConfig.DeprecatedRPC.Enabled {
|
||||
atomic.StoreInt32(&m.restStarted, 0)
|
||||
return fmt.Errorf("rest %w", errServerDisabled)
|
||||
}
|
||||
log.Debugf(log.RESTSys,
|
||||
"Deprecated RPC handler support enabled. Listen URL: http://%s:%d\n",
|
||||
common.ExtractHostOrDefault(m.restListenAddress),
|
||||
common.ExtractPortOrDefault(m.restListenAddress),
|
||||
)
|
||||
m.restRouter = m.newRouter(true)
|
||||
if m.restHTTPServer == nil {
|
||||
m.restHTTPServer = &http.Server{
|
||||
Addr: m.restListenAddress,
|
||||
Handler: m.restRouter,
|
||||
ReadHeaderTimeout: time.Minute,
|
||||
}
|
||||
}
|
||||
m.wgRest.Go(func() {
|
||||
err := m.restHTTPServer.ListenAndServe()
|
||||
if err != nil {
|
||||
atomic.StoreInt32(&m.restStarted, 0)
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Errorln(log.APIServerMgr, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// restLogger logs the requests internally
|
||||
func restLogger(inner http.Handler, name string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
inner.ServeHTTP(w, r)
|
||||
|
||||
log.Debugf(log.RESTSys,
|
||||
"%s\t%s\t%s\t%s",
|
||||
r.Method,
|
||||
r.RequestURI,
|
||||
name,
|
||||
time.Since(start),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// writeResponse outputs a JSON response of the response interface
|
||||
func writeResponse(w http.ResponseWriter, response any) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// handleError prints the REST method and error
|
||||
func handleError(method string, err error) {
|
||||
log.Errorf(log.APIServerMgr, "RESTful %s: handler failed to send JSON response. Error %s\n",
|
||||
method, err)
|
||||
}
|
||||
|
||||
// restGetAllSettings replies to a request with an encoded JSON response about the
|
||||
// trading Bots configuration.
|
||||
func (m *apiServerManager) restGetAllSettings(w http.ResponseWriter, r *http.Request) {
|
||||
err := writeResponse(w, config.GetConfig())
|
||||
if err != nil {
|
||||
handleError(r.Method, err)
|
||||
}
|
||||
}
|
||||
|
||||
// restSaveAllSettings saves all current settings from request body as a JSON
|
||||
// document then reloads state and returns the settings
|
||||
func (m *apiServerManager) restSaveAllSettings(w http.ResponseWriter, r *http.Request) {
|
||||
// Get the data from the request
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
var responseData config.Post
|
||||
err := decoder.Decode(&responseData)
|
||||
if err != nil {
|
||||
handleError(r.Method, err)
|
||||
}
|
||||
// Save change the settings
|
||||
cfg := config.GetConfig()
|
||||
err = cfg.UpdateConfig(m.gctConfigPath, &responseData.Data, false)
|
||||
if err != nil {
|
||||
handleError(r.Method, err)
|
||||
}
|
||||
|
||||
err = writeResponse(w, cfg)
|
||||
if err != nil {
|
||||
handleError(r.Method, err)
|
||||
}
|
||||
err = m.bot.SetupExchanges()
|
||||
if err != nil {
|
||||
handleError(r.Method, err)
|
||||
}
|
||||
}
|
||||
|
||||
// restGetAllActiveOrderbooks returns all enabled exchange orderbooks
|
||||
func (m *apiServerManager) restGetAllActiveOrderbooks(w http.ResponseWriter, r *http.Request) {
|
||||
var response AllEnabledExchangeOrderbooks
|
||||
response.Data = getAllActiveOrderbooks(m.exchangeManager)
|
||||
err := writeResponse(w, response)
|
||||
if err != nil {
|
||||
handleError(r.Method, err)
|
||||
}
|
||||
}
|
||||
|
||||
// restGetPortfolio returns the Bot portfolio manager
|
||||
func (m *apiServerManager) restGetPortfolio(w http.ResponseWriter, r *http.Request) {
|
||||
result := m.portfolioManager.GetPortfolioSummary()
|
||||
err := writeResponse(w, result)
|
||||
if err != nil {
|
||||
handleError(r.Method, err)
|
||||
}
|
||||
}
|
||||
|
||||
// restGetAllActiveTickers returns all active tickers
|
||||
func (m *apiServerManager) restGetAllActiveTickers(w http.ResponseWriter, r *http.Request) {
|
||||
var response AllEnabledExchangeCurrencies
|
||||
response.Data = getAllActiveTickers(m.exchangeManager)
|
||||
err := writeResponse(w, response)
|
||||
if err != nil {
|
||||
handleError(r.Method, err)
|
||||
}
|
||||
}
|
||||
|
||||
// restGetAllEnabledAccountInfo via get request returns JSON response of account
|
||||
// info
|
||||
func (m *apiServerManager) restGetAllEnabledAccountInfo(w http.ResponseWriter, r *http.Request) {
|
||||
response := getAllActiveAccounts(m.exchangeManager)
|
||||
err := writeResponse(w, response)
|
||||
if err != nil {
|
||||
handleError(r.Method, err)
|
||||
}
|
||||
}
|
||||
|
||||
// getIndex returns an HTML snippet for when a user requests the index URL
|
||||
func (m *apiServerManager) getIndex(w http.ResponseWriter, _ *http.Request) {
|
||||
_, err := fmt.Fprint(w, restIndexResponse)
|
||||
if err != nil {
|
||||
log.Errorln(log.APIServerMgr, err)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// getAllActiveOrderbooks returns all enabled exchanges orderbooks
|
||||
func getAllActiveOrderbooks(m iExchangeManager) []EnabledExchangeOrderbooks {
|
||||
exchanges, err := m.GetExchanges()
|
||||
if err != nil {
|
||||
log.Errorf(log.APIServerMgr, "Cannot get exchanges: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
orderbookData := make([]EnabledExchangeOrderbooks, 0, len(exchanges))
|
||||
for _, e := range exchanges {
|
||||
var orderbooks []orderbook.Book
|
||||
for _, a := range e.GetAssetTypes(true) {
|
||||
pairs, err := e.GetEnabledPairs(a)
|
||||
if err != nil {
|
||||
log.Errorf(log.APIServerMgr, "Exchange %s could not retrieve enabled currencies. Err: %s\n", e.GetName(), err)
|
||||
continue
|
||||
}
|
||||
for _, pair := range pairs {
|
||||
ob, err := e.GetCachedOrderbook(pair, a)
|
||||
if err != nil {
|
||||
log.Errorf(log.APIServerMgr, "Exchange %s failed to retrieve %s orderbook. Err: %s\n", e.GetName(), pair, err)
|
||||
continue
|
||||
}
|
||||
orderbooks = append(orderbooks, *ob)
|
||||
}
|
||||
}
|
||||
orderbookData = append(orderbookData, EnabledExchangeOrderbooks{ExchangeName: e.GetName(), ExchangeValues: orderbooks})
|
||||
}
|
||||
return orderbookData
|
||||
}
|
||||
|
||||
// getAllActiveTickers returns all enabled exchanges tickers
|
||||
func getAllActiveTickers(m iExchangeManager) []EnabledExchangeCurrencies {
|
||||
exchanges, err := m.GetExchanges()
|
||||
if err != nil {
|
||||
log.Errorf(log.APIServerMgr, "Cannot get exchanges: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
exchangeTickers := make([]EnabledExchangeCurrencies, 0, len(exchanges))
|
||||
for _, e := range exchanges {
|
||||
var tickers []*ticker.Price
|
||||
for _, a := range e.GetAssetTypes(true) {
|
||||
pairs, err := e.GetEnabledPairs(a)
|
||||
if err != nil {
|
||||
log.Errorf(log.APIServerMgr, "Exchange %s could not retrieve enabled currencies. Err: %s\n", e.GetName(), err)
|
||||
continue
|
||||
}
|
||||
for _, pair := range pairs {
|
||||
t, err := e.GetCachedTicker(pair, a)
|
||||
if err != nil {
|
||||
log.Errorf(log.APIServerMgr, "Exchange %s failed to retrieve %s ticker. Err: %s\n", e.GetName(), pair.String(), err)
|
||||
continue
|
||||
}
|
||||
tickers = append(tickers, t)
|
||||
}
|
||||
}
|
||||
exchangeTickers = append(exchangeTickers, EnabledExchangeCurrencies{ExchangeName: e.GetName(), ExchangeValues: tickers})
|
||||
}
|
||||
return exchangeTickers
|
||||
}
|
||||
|
||||
// getAllActiveAccounts returns all enabled exchanges accounts
|
||||
func getAllActiveAccounts(m iExchangeManager) []AllEnabledExchangeAccounts {
|
||||
exchanges, err := m.GetExchanges()
|
||||
if err != nil {
|
||||
log.Errorf(log.APIServerMgr, "Cannot get exchanges: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
accounts := make([]AllEnabledExchangeAccounts, 0, len(exchanges))
|
||||
for x := range exchanges {
|
||||
assets := exchanges[x].GetAssetTypes(true)
|
||||
exchName := exchanges[x].GetName()
|
||||
var exchangeAccounts AllEnabledExchangeAccounts
|
||||
for y := range assets {
|
||||
a, err := exchanges[x].GetCachedAccountInfo(context.TODO(), assets[y])
|
||||
if err != nil {
|
||||
log.Errorf(log.APIServerMgr,
|
||||
"Exchange %s failed to retrieve %s ticker. Err: %s\n",
|
||||
exchName,
|
||||
assets[y],
|
||||
err)
|
||||
continue
|
||||
}
|
||||
exchangeAccounts.Data = append(exchangeAccounts.Data, a)
|
||||
}
|
||||
accounts = append(accounts, exchangeAccounts)
|
||||
}
|
||||
return accounts
|
||||
}
|
||||
|
||||
// StartWebsocketServer starts a Websocket handler
|
||||
func (m *apiServerManager) StartWebsocketServer() error {
|
||||
if !atomic.CompareAndSwapInt32(&m.websocketStarted, 0, 1) {
|
||||
return fmt.Errorf("websocket server %w", errAlreadyRunning)
|
||||
}
|
||||
if !m.remoteConfig.WebsocketRPC.Enabled {
|
||||
atomic.StoreInt32(&m.websocketStarted, 0)
|
||||
return fmt.Errorf("websocket %w", errServerDisabled)
|
||||
}
|
||||
log.Debugf(log.APIServerMgr,
|
||||
"Websocket RPC support enabled. Listen URL: ws://%s:%d/ws\n",
|
||||
common.ExtractHostOrDefault(m.websocketListenAddress),
|
||||
common.ExtractPortOrDefault(m.websocketListenAddress),
|
||||
)
|
||||
m.websocketRouter = m.newRouter(false)
|
||||
if m.websocketHTTPServer == nil {
|
||||
m.websocketHTTPServer = &http.Server{
|
||||
Addr: m.websocketListenAddress,
|
||||
Handler: m.websocketRouter,
|
||||
ReadHeaderTimeout: time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
m.wgWebsocket.Go(func() {
|
||||
err := m.websocketHTTPServer.ListenAndServe()
|
||||
if err != nil {
|
||||
atomic.StoreInt32(&m.websocketStarted, 0)
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Errorln(log.APIServerMgr, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// newWebsocketHub Creates a new websocket hub
|
||||
func newWebsocketHub() *websocketHub {
|
||||
return &websocketHub{
|
||||
Broadcast: make(chan []byte),
|
||||
Register: make(chan *websocketClient),
|
||||
Unregister: make(chan *websocketClient),
|
||||
Clients: make(map[*websocketClient]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *websocketHub) run() {
|
||||
for {
|
||||
select {
|
||||
case client := <-h.Register:
|
||||
h.Clients[client] = true
|
||||
case client := <-h.Unregister:
|
||||
if _, ok := h.Clients[client]; ok {
|
||||
log.Debugln(log.APIServerMgr, "websocket: disconnected client")
|
||||
delete(h.Clients, client)
|
||||
close(client.Send)
|
||||
}
|
||||
case message := <-h.Broadcast:
|
||||
for client := range h.Clients {
|
||||
select {
|
||||
case client.Send <- message:
|
||||
default:
|
||||
log.Debugln(log.APIServerMgr, "websocket: disconnected client")
|
||||
close(client.Send)
|
||||
delete(h.Clients, client)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendWebsocketMessage sends a websocket event to the client
|
||||
func (c *websocketClient) SendWebsocketMessage(evt any) error {
|
||||
data, err := json.Marshal(evt)
|
||||
if err != nil {
|
||||
log.Errorf(log.APIServerMgr, "websocket: failed to send message: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
c.Send <- data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *websocketClient) read() {
|
||||
defer func() {
|
||||
c.Hub.Unregister <- c
|
||||
conErr := c.Conn.Close()
|
||||
if conErr != nil {
|
||||
log.Errorln(log.APIServerMgr, conErr)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
msgType, message, err := c.Conn.ReadMessage()
|
||||
if err != nil {
|
||||
if gws.IsUnexpectedCloseError(err, gws.CloseGoingAway, gws.CloseAbnormalClosure) {
|
||||
log.Errorf(log.APIServerMgr, "websocket: client disconnected, err: %s\n", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if msgType == gws.TextMessage {
|
||||
var evt WebsocketEvent
|
||||
err := json.Unmarshal(message, &evt)
|
||||
if err != nil {
|
||||
log.Errorf(log.APIServerMgr, "websocket: failed to decode JSON sent from client %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if evt.Event == "" {
|
||||
log.Warnln(log.APIServerMgr, "websocket: client sent a blank event, disconnecting")
|
||||
continue
|
||||
}
|
||||
|
||||
dataJSON, err := json.Marshal(evt.Data)
|
||||
if err != nil {
|
||||
log.Errorln(log.APIServerMgr, "websocket: client sent data we couldn't JSON decode")
|
||||
break
|
||||
}
|
||||
|
||||
req := strings.ToLower(evt.Event)
|
||||
log.Debugf(log.APIServerMgr, "websocket: request received: %s\n", req)
|
||||
|
||||
result, ok := wsHandlers[req]
|
||||
if !ok {
|
||||
log.Debugln(log.APIServerMgr, "websocket: unsupported event")
|
||||
continue
|
||||
}
|
||||
|
||||
if result.authRequired && !c.Authenticated {
|
||||
log.Warnf(log.APIServerMgr, "Websocket: request %s failed due to unauthenticated request on an authenticated API\n", evt.Event)
|
||||
err = c.SendWebsocketMessage(WebsocketEventResponse{Event: evt.Event, Error: "unauthorised request on authenticated API"})
|
||||
if err != nil {
|
||||
log.Errorln(log.APIServerMgr, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err = result.handler(c, dataJSON)
|
||||
if err != nil {
|
||||
log.Errorf(log.APIServerMgr, "websocket: request %s failed. Error %s\n", evt.Event, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *websocketClient) write() {
|
||||
defer func() {
|
||||
err := c.Conn.Close()
|
||||
if err != nil {
|
||||
log.Errorln(log.APIServerMgr, err)
|
||||
}
|
||||
}()
|
||||
for {
|
||||
message, ok := <-c.Send
|
||||
if !ok {
|
||||
err := c.Conn.WriteMessage(gws.CloseMessage, []byte{})
|
||||
if err != nil {
|
||||
log.Errorln(log.APIServerMgr, err)
|
||||
}
|
||||
log.Debugln(log.APIServerMgr, "websocket: hub closed the channel")
|
||||
return
|
||||
}
|
||||
|
||||
w, err := c.Conn.NextWriter(gws.TextMessage)
|
||||
if err != nil {
|
||||
log.Errorf(log.APIServerMgr, "websocket: failed to create new io.writeCloser: %s\n", err)
|
||||
return
|
||||
}
|
||||
_, err = w.Write(message)
|
||||
if err != nil {
|
||||
log.Errorln(log.APIServerMgr, err)
|
||||
}
|
||||
|
||||
// Add queued chat messages to the current websocket message
|
||||
n := len(c.Send)
|
||||
for range n {
|
||||
_, err = w.Write(<-c.Send)
|
||||
if err != nil {
|
||||
log.Errorln(log.APIServerMgr, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
log.Errorf(log.APIServerMgr, "websocket: failed to close io.WriteCloser: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StartWebsocketHandler starts the websocket hub and routine which
|
||||
// handles clients
|
||||
func StartWebsocketHandler() {
|
||||
if !wsHubStarted {
|
||||
wsHubStarted = true
|
||||
wsHub = newWebsocketHub()
|
||||
go wsHub.run()
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastWebsocketMessage meow
|
||||
func BroadcastWebsocketMessage(evt WebsocketEvent) error {
|
||||
if !wsHubStarted {
|
||||
return ErrWebsocketServiceNotRunning
|
||||
}
|
||||
|
||||
data, err := json.Marshal(evt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wsHub.Broadcast <- data
|
||||
return nil
|
||||
}
|
||||
|
||||
// WebsocketClientHandler upgrades the HTTP connection to a websocket
|
||||
// compatible one
|
||||
func (m *apiServerManager) WebsocketClientHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !wsHubStarted {
|
||||
StartWebsocketHandler()
|
||||
}
|
||||
|
||||
connectionLimit := m.remoteConfig.WebsocketRPC.ConnectionLimit
|
||||
numClients := len(wsHub.Clients)
|
||||
|
||||
if numClients >= connectionLimit {
|
||||
log.Warnf(log.APIServerMgr,
|
||||
"websocket: client rejected due to websocket client limit reached. Number of clients %d. Limit %d.\n",
|
||||
numClients, connectionLimit)
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
upgrader := gws.Upgrader{
|
||||
WriteBufferSize: 1024,
|
||||
ReadBufferSize: 1024,
|
||||
}
|
||||
|
||||
// Allow insecure origin if the Origin request header is present and not
|
||||
// equal to the Host request header. Default to false
|
||||
if m.remoteConfig.WebsocketRPC.AllowInsecureOrigin {
|
||||
upgrader.CheckOrigin = func(*http.Request) bool { return true }
|
||||
}
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Errorln(log.APIServerMgr, err)
|
||||
return
|
||||
}
|
||||
|
||||
client := &websocketClient{
|
||||
Hub: wsHub,
|
||||
Conn: conn,
|
||||
Send: make(chan []byte, 1024),
|
||||
maxAuthFailures: m.remoteConfig.WebsocketRPC.MaxAuthFailures,
|
||||
username: m.remoteConfig.Username,
|
||||
password: m.remoteConfig.Password,
|
||||
configPath: m.gctConfigPath,
|
||||
exchangeManager: m.exchangeManager,
|
||||
bot: m.bot,
|
||||
portfolioManager: m.portfolioManager,
|
||||
}
|
||||
|
||||
client.Hub.Register <- client
|
||||
log.Debugf(log.APIServerMgr,
|
||||
"websocket: client connected. Connected clients: %d. Limit %d.\n",
|
||||
numClients+1, connectionLimit)
|
||||
|
||||
go client.read()
|
||||
go client.write()
|
||||
}
|
||||
|
||||
func wsAuth(client *websocketClient, data any) error {
|
||||
d, ok := data.([]byte)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("[]byte", data)
|
||||
}
|
||||
|
||||
wsResp := WebsocketEventResponse{
|
||||
Event: "auth",
|
||||
}
|
||||
|
||||
var auth WebsocketAuth
|
||||
err := json.Unmarshal(d, &auth)
|
||||
if err != nil {
|
||||
wsResp.Error = err.Error()
|
||||
sendErr := client.SendWebsocketMessage(wsResp)
|
||||
if sendErr != nil {
|
||||
log.Errorln(log.APIServerMgr, sendErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
shasum := sha256.Sum256([]byte(client.password))
|
||||
if auth.Username == client.username && auth.Password == hex.EncodeToString(shasum[:]) {
|
||||
client.Authenticated = true
|
||||
wsResp.Data = WebsocketResponseSuccess
|
||||
log.Debugln(log.APIServerMgr,
|
||||
"websocket: client authenticated successfully")
|
||||
return client.SendWebsocketMessage(wsResp)
|
||||
}
|
||||
|
||||
wsResp.Error = "invalid username/password"
|
||||
client.authFailures++
|
||||
sendErr := client.SendWebsocketMessage(wsResp)
|
||||
if sendErr != nil {
|
||||
log.Errorln(log.APIServerMgr, sendErr)
|
||||
}
|
||||
if client.authFailures >= client.maxAuthFailures {
|
||||
log.Debugf(log.APIServerMgr,
|
||||
"websocket: disconnecting client, maximum auth failures threshold reached (failures: %d limit: %d)\n",
|
||||
client.authFailures, client.maxAuthFailures)
|
||||
wsHub.Unregister <- client
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf(log.APIServerMgr,
|
||||
"websocket: client sent wrong username/password (failures: %d limit: %d)\n",
|
||||
client.authFailures, client.maxAuthFailures)
|
||||
return nil
|
||||
}
|
||||
|
||||
func wsGetConfig(client *websocketClient, _ any) error {
|
||||
wsResp := WebsocketEventResponse{
|
||||
Event: "GetConfig",
|
||||
Data: config.GetConfig(),
|
||||
}
|
||||
return client.SendWebsocketMessage(wsResp)
|
||||
}
|
||||
|
||||
func wsSaveConfig(client *websocketClient, data any) error {
|
||||
d, ok := data.([]byte)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("[]byte", data)
|
||||
}
|
||||
|
||||
wsResp := WebsocketEventResponse{
|
||||
Event: "SaveConfig",
|
||||
}
|
||||
var respCfg config.Config
|
||||
err := json.Unmarshal(d, &respCfg)
|
||||
if err != nil {
|
||||
wsResp.Error = err.Error()
|
||||
sendErr := client.SendWebsocketMessage(wsResp)
|
||||
if sendErr != nil {
|
||||
log.Errorln(log.APIServerMgr, sendErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := config.GetConfig()
|
||||
err = cfg.UpdateConfig(client.configPath, &respCfg, false)
|
||||
if err != nil {
|
||||
wsResp.Error = err.Error()
|
||||
sendErr := client.SendWebsocketMessage(wsResp)
|
||||
if sendErr != nil {
|
||||
log.Errorln(log.APIServerMgr, sendErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
err = client.bot.SetupExchanges()
|
||||
if err != nil {
|
||||
wsResp.Error = err.Error()
|
||||
sendErr := client.SendWebsocketMessage(wsResp)
|
||||
if sendErr != nil {
|
||||
log.Errorln(log.APIServerMgr, sendErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
wsResp.Data = WebsocketResponseSuccess
|
||||
return client.SendWebsocketMessage(wsResp)
|
||||
}
|
||||
|
||||
func wsGetAccountInfo(client *websocketClient, _ any) error {
|
||||
accountInfo := getAllActiveAccounts(client.exchangeManager)
|
||||
wsResp := WebsocketEventResponse{
|
||||
Event: "GetAccountInfo",
|
||||
Data: accountInfo,
|
||||
}
|
||||
return client.SendWebsocketMessage(wsResp)
|
||||
}
|
||||
|
||||
func wsGetTickers(client *websocketClient, _ any) error {
|
||||
wsResp := WebsocketEventResponse{
|
||||
Event: "GetTickers",
|
||||
}
|
||||
wsResp.Data = getAllActiveTickers(client.exchangeManager)
|
||||
return client.SendWebsocketMessage(wsResp)
|
||||
}
|
||||
|
||||
func wsGetTicker(client *websocketClient, data any) error {
|
||||
d, ok := data.([]byte)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("[]byte", data)
|
||||
}
|
||||
|
||||
wsResp := WebsocketEventResponse{
|
||||
Event: "GetTicker",
|
||||
}
|
||||
var tickerReq WebsocketOrderbookTickerRequest
|
||||
err := json.Unmarshal(d, &tickerReq)
|
||||
if err != nil {
|
||||
wsResp.Error = err.Error()
|
||||
sendErr := client.SendWebsocketMessage(wsResp)
|
||||
if sendErr != nil {
|
||||
log.Errorln(log.APIServerMgr, sendErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := currency.NewPairFromString(tickerReq.Currency)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a, err := asset.New(tickerReq.AssetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exch, err := client.exchangeManager.GetExchangeByName(tickerReq.Exchange)
|
||||
if err != nil {
|
||||
wsResp.Error = err.Error()
|
||||
sendErr := client.SendWebsocketMessage(wsResp)
|
||||
if sendErr != nil {
|
||||
log.Errorln(log.APIServerMgr, sendErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
tick, err := exch.GetCachedTicker(p, a)
|
||||
if err != nil {
|
||||
wsResp.Error = err.Error()
|
||||
sendErr := client.SendWebsocketMessage(wsResp)
|
||||
if sendErr != nil {
|
||||
log.Errorln(log.APIServerMgr, sendErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
wsResp.Data = tick
|
||||
return client.SendWebsocketMessage(wsResp)
|
||||
}
|
||||
|
||||
func wsGetOrderbooks(client *websocketClient, _ any) error {
|
||||
wsResp := WebsocketEventResponse{
|
||||
Event: "GetOrderbooks",
|
||||
}
|
||||
wsResp.Data = getAllActiveOrderbooks(client.exchangeManager)
|
||||
return client.SendWebsocketMessage(wsResp)
|
||||
}
|
||||
|
||||
func wsGetOrderbook(client *websocketClient, data any) error {
|
||||
d, ok := data.([]byte)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("[]byte", data)
|
||||
}
|
||||
|
||||
var orderbookReq WebsocketOrderbookTickerRequest
|
||||
err := json.Unmarshal(d, &orderbookReq)
|
||||
if err != nil {
|
||||
sendErr := client.SendWebsocketMessage(WebsocketEventResponse{Event: "GetOrderbook", Error: err.Error()})
|
||||
if sendErr != nil {
|
||||
log.Errorln(log.APIServerMgr, sendErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := currency.NewPairFromString(orderbookReq.Currency)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a, err := asset.New(orderbookReq.AssetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exch, err := client.exchangeManager.GetExchangeByName(orderbookReq.Exchange)
|
||||
if err != nil {
|
||||
sendErr := client.SendWebsocketMessage(WebsocketEventResponse{Event: "GetOrderbook", Error: err.Error()})
|
||||
if sendErr != nil {
|
||||
log.Errorln(log.APIServerMgr, sendErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
ob, err := exch.GetCachedOrderbook(p, a)
|
||||
if err != nil {
|
||||
sendErr := client.SendWebsocketMessage(WebsocketEventResponse{Event: "GetOrderbook", Error: err.Error()})
|
||||
if sendErr != nil {
|
||||
log.Errorln(log.APIServerMgr, sendErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return client.SendWebsocketMessage(WebsocketEventResponse{Event: "GetOrderbook", Data: ob})
|
||||
}
|
||||
|
||||
func wsGetExchangeRates(client *websocketClient, _ any) error {
|
||||
wsResp := WebsocketEventResponse{
|
||||
Event: "GetExchangeRates",
|
||||
}
|
||||
|
||||
var err error
|
||||
wsResp.Data, err = currency.GetExchangeRates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.SendWebsocketMessage(wsResp)
|
||||
}
|
||||
|
||||
func wsGetPortfolio(client *websocketClient, _ any) error {
|
||||
wsResp := WebsocketEventResponse{
|
||||
Event: "GetPortfolio",
|
||||
}
|
||||
|
||||
wsResp.Data = client.portfolioManager.GetPortfolioSummary()
|
||||
return client.SendWebsocketMessage(wsResp)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
# GoCryptoTrader package Apiserver
|
||||
|
||||
<img src="/common/gctlogo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/engine/apiserver)
|
||||
[](https://codecov.io/gh/thrasher-corp/gocryptotrader)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This apiserver package is part of the GoCryptoTrader codebase.
|
||||
|
||||
## This is still in active development
|
||||
|
||||
You can track ideas, planned features and what's in progress on our [GoCryptoTrader Kanban board](https://github.com/orgs/thrasher-corp/projects/3).
|
||||
|
||||
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/zt-38z8abs3l-gH8AAOk8XND6DP5NfCiG_g)
|
||||
|
||||
## Current Features for Apiserver
|
||||
+ The API server subsystem is a deprecated service used to host a REST or websocket server to interact with some functions of GoCryptoTrader
|
||||
+ This subsystem is no longer maintained and it is highly encouraged to interact with GRPC endpoints directly where possible
|
||||
+ In order to modify the behaviour of the API server subsystem, you can edit the following inside your config file:
|
||||
|
||||
### deprecatedRPC
|
||||
|
||||
| Config | Description | Example |
|
||||
| ------ | ----------- | ------- |
|
||||
| enabled | If enabled will create a REST server which will listen to commands on the listen address | `true` |
|
||||
| listenAddress | If enabled will listen for REST requests on this address and return a JSON response | `localhost:9050` |
|
||||
|
||||
### websocketRPC
|
||||
|
||||
| Config | Description | Example |
|
||||
| ------ | ----------- | ------- |
|
||||
| enabled | If enabled will create a REST server which will listen to commands on the listen address | `true` |
|
||||
| listenAddress | If enabled will listen for requests on this address and return a JSON response | `localhost:9051` |
|
||||
| connectionLimit | Defines how many connections the websocket RPC server can handle simultanesoly | `1` |
|
||||
| maxAuthFailures | For authenticated endpoints, the amount of failed attempts allowed before disconnection | `3` |
|
||||
| allowInsecureOrigin | Allows use of insecure connections | `true` |
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
|
||||
@@ -1,224 +0,0 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/encoding/json"
|
||||
)
|
||||
|
||||
func TestSetupAPIServerManager(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := setupAPIServerManager(nil, nil, nil, nil, nil, "")
|
||||
assert.ErrorIs(t, err, errNilRemoteConfig)
|
||||
|
||||
_, err = setupAPIServerManager(&config.RemoteControlConfig{}, nil, nil, nil, nil, "")
|
||||
assert.ErrorIs(t, err, errNilPProfConfig)
|
||||
|
||||
_, err = setupAPIServerManager(&config.RemoteControlConfig{}, &config.Profiler{}, nil, nil, nil, "")
|
||||
assert.ErrorIs(t, err, errNilExchangeManager)
|
||||
|
||||
_, err = setupAPIServerManager(&config.RemoteControlConfig{}, &config.Profiler{}, &ExchangeManager{}, nil, nil, "")
|
||||
assert.ErrorIs(t, err, errNilBot)
|
||||
|
||||
_, err = setupAPIServerManager(&config.RemoteControlConfig{}, &config.Profiler{}, &ExchangeManager{}, &fakeBot{}, nil, "")
|
||||
assert.ErrorIs(t, err, errEmptyConfigPath)
|
||||
|
||||
wd, _ := os.Getwd()
|
||||
_, err = setupAPIServerManager(&config.RemoteControlConfig{}, &config.Profiler{}, &ExchangeManager{}, &fakeBot{}, nil, wd)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestStartRESTServer(t *testing.T) {
|
||||
t.Parallel()
|
||||
wd, _ := os.Getwd()
|
||||
m, err := setupAPIServerManager(&config.RemoteControlConfig{}, &config.Profiler{}, &ExchangeManager{}, &fakeBot{}, nil, wd)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = m.StartRESTServer()
|
||||
assert.ErrorIs(t, err, errServerDisabled)
|
||||
|
||||
m.remoteConfig.DeprecatedRPC.Enabled = true
|
||||
err = m.StartRESTServer()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestStartWebsocketServer(t *testing.T) {
|
||||
t.Parallel()
|
||||
wd, _ := os.Getwd()
|
||||
m, err := setupAPIServerManager(&config.RemoteControlConfig{}, &config.Profiler{}, &ExchangeManager{}, &fakeBot{}, nil, wd)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = m.StartWebsocketServer()
|
||||
assert.ErrorIs(t, err, errServerDisabled)
|
||||
|
||||
m.remoteConfig.WebsocketRPC.Enabled = true
|
||||
err = m.StartWebsocketServer()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestStopRESTServer(t *testing.T) {
|
||||
t.Parallel()
|
||||
wd, _ := os.Getwd()
|
||||
m, err := setupAPIServerManager(&config.RemoteControlConfig{
|
||||
DeprecatedRPC: config.DepcrecatedRPCConfig{
|
||||
Enabled: true,
|
||||
ListenAddress: "localhost:9051",
|
||||
},
|
||||
}, &config.Profiler{}, &ExchangeManager{}, &fakeBot{}, nil, wd)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = m.StopRESTServer()
|
||||
assert.ErrorIs(t, err, ErrSubSystemNotStarted)
|
||||
|
||||
err = m.StartRESTServer()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = m.StopRESTServer()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// do it again to ensure things have reset appropriately and no errors occur starting
|
||||
err = m.StartRESTServer()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = m.StopRESTServer()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestWebsocketStop(t *testing.T) {
|
||||
t.Parallel()
|
||||
wd, _ := os.Getwd()
|
||||
m, err := setupAPIServerManager(&config.RemoteControlConfig{
|
||||
WebsocketRPC: config.WebsocketRPCConfig{
|
||||
Enabled: true,
|
||||
ListenAddress: "localhost:9052",
|
||||
},
|
||||
}, &config.Profiler{}, &ExchangeManager{}, &fakeBot{}, nil, wd)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = m.StopWebsocketServer()
|
||||
assert.ErrorIs(t, err, ErrSubSystemNotStarted)
|
||||
|
||||
err = m.StartWebsocketServer()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = m.StopWebsocketServer()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// do it again to ensure things have reset appropriately and no errors occur starting
|
||||
err = m.StartWebsocketServer()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = m.StopWebsocketServer()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestIsRESTServerRunning(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := &apiServerManager{}
|
||||
assert.False(t, m.IsRESTServerRunning(), "should return correctly with empty type")
|
||||
m.restStarted = 1
|
||||
assert.True(t, m.IsRESTServerRunning(), "should return correctly with restStarted set")
|
||||
assert.False(t, (*apiServerManager)(nil).IsRESTServerRunning(), "should return correctly on nil type")
|
||||
}
|
||||
|
||||
func TestIsWebsocketServerRunning(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := &apiServerManager{}
|
||||
assert.False(t, m.IsWebsocketServerRunning(), "should return correctly with empty type")
|
||||
m.websocketStarted = 1
|
||||
assert.True(t, m.IsWebsocketServerRunning(), "should return correctly with websocketStarted set")
|
||||
assert.False(t, (*apiServerManager)(nil).IsWebsocketServerRunning(), "should return correctly on nil type")
|
||||
}
|
||||
|
||||
func TestGetAllActiveOrderbooks(t *testing.T) {
|
||||
man := NewExchangeManager()
|
||||
bs, err := man.NewExchangeByName("Bitstamp")
|
||||
require.NoError(t, err, "NewExchangeByName must not error")
|
||||
bs.SetDefaults()
|
||||
err = man.Add(bs)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := getAllActiveOrderbooks(man)
|
||||
assert.NotNil(t, resp)
|
||||
}
|
||||
|
||||
func TestGetAllActiveTickers(t *testing.T) {
|
||||
t.Parallel()
|
||||
man := NewExchangeManager()
|
||||
bs, err := man.NewExchangeByName("Bitstamp")
|
||||
require.NoError(t, err, "NewExchangeByName must not error")
|
||||
bs.SetDefaults()
|
||||
err = man.Add(bs)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := getAllActiveTickers(man)
|
||||
assert.NotNil(t, resp)
|
||||
}
|
||||
|
||||
func TestGetAllActiveAccounts(t *testing.T) {
|
||||
t.Parallel()
|
||||
man := NewExchangeManager()
|
||||
bs, err := man.NewExchangeByName("Bitstamp")
|
||||
require.NoError(t, err, "NewExchangeByName must not error")
|
||||
bs.SetDefaults()
|
||||
err = man.Add(bs)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := getAllActiveAccounts(man)
|
||||
assert.NotNil(t, resp)
|
||||
}
|
||||
|
||||
func makeHTTPGetRequest(t *testing.T, response any) *http.Response {
|
||||
t.Helper()
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
err := writeResponse(w, response)
|
||||
require.NoError(t, err)
|
||||
|
||||
return w.Result()
|
||||
}
|
||||
|
||||
// TestConfigAllJsonResponse test if config/all restful json response is valid
|
||||
func TestConfigAllJsonResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
var c config.Config
|
||||
err := c.LoadConfig(config.TestFile, true)
|
||||
assert.NoError(t, err, "LoadConfig should not error")
|
||||
|
||||
resp := makeHTTPGetRequest(t, c)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
assert.NoError(t, err, "ReadAll should not error")
|
||||
err = resp.Body.Close()
|
||||
assert.NoError(t, err, "Close body should not error")
|
||||
|
||||
var responseConfig config.Config
|
||||
err = json.Unmarshal(body, &responseConfig)
|
||||
assert.NoError(t, err, "Unmarshal should not error")
|
||||
for i, e := range responseConfig.Exchanges {
|
||||
err = e.CurrencyPairs.SetDelimitersFromConfig()
|
||||
assert.NoError(t, err, "SetDelimitersFromConfig should not error")
|
||||
// Using require here makes it much easier to isolate differences per-exchange than below
|
||||
// We look into pointers separately
|
||||
for a, p := range e.CurrencyPairs.Pairs {
|
||||
require.Equalf(t, c.Exchanges[i].CurrencyPairs.Pairs[a], p, "%s exchange Config CurrencyManager Pairs for asset %s must match api response", e.Name, a)
|
||||
}
|
||||
require.Equalf(t, c.Exchanges[i].CurrencyPairs, e.CurrencyPairs, "%s exchange Config CurrencyManager must match api response", e.Name)
|
||||
require.Equalf(t, c.Exchanges[i], e, "%s exchange Config must match api response", e.Name) // require here makes it much easier to isolate differences than below
|
||||
}
|
||||
assert.Equal(t, c, responseConfig, "Config should match api response")
|
||||
}
|
||||
|
||||
// fakeBot is a basic implementation of the iBot interface used for testing
|
||||
type fakeBot struct{}
|
||||
|
||||
// SetupExchanges is a basic implementation of the iBot interface used for testing
|
||||
func (f *fakeBot) SetupExchanges() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
gws "github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
)
|
||||
|
||||
// Const vars for websocket
|
||||
const (
|
||||
WebsocketResponseSuccess = "OK"
|
||||
restIndexResponse = "<html>GoCryptoTrader RESTful interface. For the web GUI, please visit the <a href=https://github.com/thrasher-corp/gocryptotrader/blob/master/web/README.md>web GUI readme.</a></html>"
|
||||
DeprecatedName = "deprecated_rpc"
|
||||
WebsocketName = "websocket_rpc"
|
||||
)
|
||||
|
||||
var (
|
||||
wsHub *websocketHub
|
||||
wsHubStarted bool
|
||||
errNilRemoteConfig = errors.New("received nil remote config")
|
||||
errNilPProfConfig = errors.New("received nil pprof config")
|
||||
errNilBot = errors.New("received nil engine bot")
|
||||
errEmptyConfigPath = errors.New("received empty config path")
|
||||
errServerDisabled = errors.New("server disabled")
|
||||
errAlreadyRunning = errors.New("already running")
|
||||
// ErrWebsocketServiceNotRunning occurs when a message is sent to be broadcast via websocket
|
||||
// and its not running
|
||||
ErrWebsocketServiceNotRunning = errors.New("websocket service not started")
|
||||
)
|
||||
|
||||
// apiServerManager holds all relevant fields to manage both REST and websocket
|
||||
// api servers
|
||||
type apiServerManager struct {
|
||||
restStarted int32
|
||||
websocketStarted int32
|
||||
restListenAddress string
|
||||
websocketListenAddress string
|
||||
gctConfigPath string
|
||||
restHTTPServer *http.Server
|
||||
websocketHTTPServer *http.Server
|
||||
wgRest sync.WaitGroup
|
||||
wgWebsocket sync.WaitGroup
|
||||
|
||||
restRouter *mux.Router
|
||||
websocketRouter *mux.Router
|
||||
websocketHub *websocketHub
|
||||
|
||||
remoteConfig *config.RemoteControlConfig
|
||||
pprofConfig *config.Profiler
|
||||
exchangeManager iExchangeManager
|
||||
bot iBot
|
||||
portfolioManager iPortfolioManager
|
||||
}
|
||||
|
||||
// websocketClient stores information related to the websocket client
|
||||
type websocketClient struct {
|
||||
Hub *websocketHub
|
||||
Conn *gws.Conn
|
||||
Authenticated bool
|
||||
authFailures int
|
||||
Send chan []byte
|
||||
username string
|
||||
password string
|
||||
maxAuthFailures int
|
||||
exchangeManager iExchangeManager
|
||||
bot iBot
|
||||
portfolioManager iPortfolioManager
|
||||
configPath string
|
||||
}
|
||||
|
||||
// websocketHub stores the data for managing websocket clients
|
||||
type websocketHub struct {
|
||||
Clients map[*websocketClient]bool
|
||||
Broadcast chan []byte
|
||||
Register chan *websocketClient
|
||||
Unregister chan *websocketClient
|
||||
}
|
||||
|
||||
// WebsocketEvent is the struct used for websocket events
|
||||
type WebsocketEvent struct {
|
||||
Exchange string `json:"exchange,omitempty"`
|
||||
AssetType string `json:"assetType,omitempty"`
|
||||
Event string
|
||||
Data any
|
||||
}
|
||||
|
||||
// WebsocketEventResponse is the struct used for websocket event responses
|
||||
type WebsocketEventResponse struct {
|
||||
Event string `json:"event"`
|
||||
Data any `json:"data"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// WebsocketOrderbookTickerRequest is a struct used for ticker and orderbook
|
||||
// requests
|
||||
type WebsocketOrderbookTickerRequest struct {
|
||||
Exchange string `json:"exchangeName"`
|
||||
Currency string `json:"currency"`
|
||||
AssetType string `json:"assetType"`
|
||||
}
|
||||
|
||||
// WebsocketAuth is a struct used for
|
||||
type WebsocketAuth struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// Route is a sub type that holds the request routes
|
||||
type Route struct {
|
||||
Name string
|
||||
Method string
|
||||
Pattern string
|
||||
HandlerFunc http.HandlerFunc
|
||||
}
|
||||
|
||||
// AllEnabledExchangeOrderbooks holds the enabled exchange orderbooks
|
||||
type AllEnabledExchangeOrderbooks struct {
|
||||
Data []EnabledExchangeOrderbooks `json:"data"`
|
||||
}
|
||||
|
||||
// EnabledExchangeOrderbooks is a sub type for singular exchanges and respective
|
||||
// orderbooks
|
||||
type EnabledExchangeOrderbooks struct {
|
||||
ExchangeName string `json:"exchangeName"`
|
||||
ExchangeValues []orderbook.Book `json:"exchangeValues"`
|
||||
}
|
||||
|
||||
// AllEnabledExchangeCurrencies holds the enabled exchange currencies
|
||||
type AllEnabledExchangeCurrencies struct {
|
||||
Data []EnabledExchangeCurrencies `json:"data"`
|
||||
}
|
||||
|
||||
// EnabledExchangeCurrencies is a sub type for singular exchanges and respective
|
||||
// currencies
|
||||
type EnabledExchangeCurrencies struct {
|
||||
ExchangeName string `json:"exchangeName"`
|
||||
ExchangeValues []*ticker.Price `json:"exchangeValues"`
|
||||
}
|
||||
|
||||
// AllEnabledExchangeAccounts holds all enabled accounts info
|
||||
type AllEnabledExchangeAccounts struct {
|
||||
Data []account.Holdings `json:"data"`
|
||||
}
|
||||
|
||||
var wsHandlers = map[string]wsCommandHandler{
|
||||
"auth": {authRequired: false, handler: wsAuth},
|
||||
"getconfig": {authRequired: true, handler: wsGetConfig},
|
||||
"saveconfig": {authRequired: true, handler: wsSaveConfig},
|
||||
"getaccountinfo": {authRequired: true, handler: wsGetAccountInfo},
|
||||
"gettickers": {authRequired: false, handler: wsGetTickers},
|
||||
"getticker": {authRequired: false, handler: wsGetTicker},
|
||||
"getorderbooks": {authRequired: false, handler: wsGetOrderbooks},
|
||||
"getorderbook": {authRequired: false, handler: wsGetOrderbook},
|
||||
"getexchangerates": {authRequired: false, handler: wsGetExchangeRates},
|
||||
"getportfolio": {authRequired: true, handler: wsGetPortfolio},
|
||||
}
|
||||
|
||||
type wsCommandHandler struct {
|
||||
authRequired bool
|
||||
handler func(client *websocketClient, data any) error
|
||||
}
|
||||
@@ -68,7 +68,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ strategies.
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="/docs/assets/donate.png" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user