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:
Adrian Gallagher
2025-09-30 13:32:09 +10:00
committed by GitHub
parent 0b60693ff5
commit bb122dcafa
388 changed files with 360 additions and 23901 deletions

View File

@@ -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
View File

@@ -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/

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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"/>

View File

@@ -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">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](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:

View File

@@ -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:

View File

@@ -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}}

View File

@@ -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).

View File

@@ -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:

View File

@@ -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">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)

View File

@@ -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()
}

View File

@@ -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:

View File

@@ -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">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](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:

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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"`
@@ -216,8 +217,10 @@ type Exchange struct {
// Profiler defines the profiler configuration to enable pprof
type Profiler struct {
Enabled bool `json:"enabled"`
MutexProfileFraction int `json:"mutex_profile_fraction"`
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"`
Username string `json:"username"`
Password string `json:"password"`
GRPC GRPCConfig `json:"gRPC"`
}
// Post holds the bot configuration data

View File

@@ -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{})
}

View 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
}

View 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)
}

View File

@@ -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": {

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](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:

View File

@@ -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"

View File

@@ -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">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)

View File

@@ -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">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)

View File

@@ -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">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)

View File

@@ -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">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)

View File

@@ -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">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)

View File

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

View File

@@ -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)
}

View File

@@ -1,49 +0,0 @@
# GoCryptoTrader package Apiserver
<img src="/common/gctlogo.png?raw=true" width="350px" height="350px" hspace="70">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/engine/apiserver)
[![Coverage Status](https://codecov.io/gh/thrasher-corp/gocryptotrader/graph/badge.svg?token=41784B23TS)](https://codecov.io/gh/thrasher-corp/gocryptotrader)
[![Go Report Card](https://goreportcard.com/badge/github.com/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***

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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