Skip to content

Commit

Permalink
Merge branch 'develop' into feature/modify_priceticker
Browse files Browse the repository at this point in the history
  • Loading branch information
Luphia authored Apr 17, 2024
2 parents 5e5d894 + 689322b commit 116d08f
Show file tree
Hide file tree
Showing 24 changed files with 1,044 additions and 125 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/autotrade.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
with:
node-version: 18
- name: Install dependencies
run: npm ci
run: npm i
- name: Build nestjs app
run: npm run build
- name: Run tests
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ lerna-debug.log*
!.vscode/launch.json
!.vscode/extensions.json
request.http

# package-lock.json
package-lock.json
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ Parameters

```typescript

PUT /tradebot?id=tradebotId
GET /tradebot?id=tradebotId

```
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/schedule": "^4.0.1",
"@tensorflow/tfjs-node": "^4.17.0",
"arima": "^0.2.5",
"axios": "^1.5.1",
"bignumber.js": "^9.1.2",
Expand Down
18 changes: 18 additions & 0 deletions src/price_ticker/price_ticker.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import PriceTickerService from './price_ticker.service';
import { HttpModule } from '@nestjs/axios';
import { QuotationDto } from './dto/quotation.dto';
import * as fs from 'fs';

describe('PriceTickerService', () => {
let service: PriceTickerService;
Expand Down Expand Up @@ -36,4 +37,21 @@ describe('PriceTickerService', () => {
.mockImplementation(async () => array);
expect(await service.getCandlesticks()).toStrictEqual([1, 2, 3, 4, 5]);
});

it('should return an array of candlestick', async () => {
let etharr = [];
for (let i = 0; i < 11; i++) {
const begin = Date.now() - 90000000 * (i + 1);
const end = Date.now() - 90000000 * i;
const r = await service.getCandlesticksV2('ETH-USDT', '5m', begin, end);
const tempArr = r.data.candlesticks.candlesticks.map(
(item) => item.y.close,
);
etharr = tempArr.concat(etharr);
}
const etharrJson = JSON.stringify(etharr);
// console.log(etharr);
// use fs to write the etharr to a file
fs.writeFileSync('src/strategies/etharr.txt', etharrJson);
});
});
3 changes: 1 addition & 2 deletions src/strategies/ETH-USD.csv
Original file line number Diff line number Diff line change
Expand Up @@ -364,5 +364,4 @@ Date,Open,High,Low,Close,Adj Close,Volume
2024-03-10,3915.590576,3968.723633,3800.564453,3881.193115,3881.193115,15783924355
2024-03-11,3881.237793,4087.050049,3745.125244,4066.445068,4066.445068,28806262507
2024-03-12,4066.690430,4092.284180,3831.889893,3980.273193,3980.273193,26917010932
2024-03-13,3980.265137,4083.007324,3936.627197,4006.457031,4006.457031,22028114691
2024-03-14,null,null,null,null,null,null
2024-03-13,3980.265137,4083.007324,3936.627197,4006.457031,4006.457031,22028114691
1 change: 1 addition & 0 deletions src/strategies/ethCandle.txt

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/strategies/etharr.txt

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion src/strategies/strategies.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Controller } from '@nestjs/common';
import { Controller, Get } from '@nestjs/common';
import { StrategiesService } from './strategies.service';

@Controller('strategies')
export class StrategiesController {
constructor(private readonly strategiesService: StrategiesService) {}

@Get()
async trainDqn() {
await this.strategiesService.trainDqn();
return 'Training DQN finished';
}
}
46 changes: 5 additions & 41 deletions src/strategies/strategies.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,58 +116,22 @@ describe('StrategiesService', () => {
// Info: (20240320 - Jacky) this is aim to sum the profit of all trades
const profitArray = tradeArray.map((trade) => trade.profit);
const sum = profitArray.reduce((total, profit) => total + profit, 0);
expect(sum).toBe(-1069.9531315100012);
expect(sum).toBe(-1060.9868554650009);
});

it('should backtest use api', async () => {
console.log = () => {};
const ETHPriceArray = [
3250.92, 3227.38, 3264, 3252.43, 3277.55, 3264.01, 3280.58, 3260.4,
3258.96, 3275.94, 3292.73, 3281.7, 3273.62, 3259.7, 3222.2, 3228.48,
3225.21, 3229.59, 3228, 3230.22, 3245.07, 3244.62, 3227.1, 3213.34,
3205.78, 3218.81, 3212.59, 3216.6, 3243.82, 3254.6, 3260.18, 3276.6,
3273.45, 3276.47, 3270, 3289.41, 3278.61, 3257.55, 3272.34, 3264.35,
3278.03, 3292.56, 3303.6, 3295.36, 3305.09, 3295.91, 3282.62, 3279.36,
3279.6, 3273.79, 3296.25, 3306.6, 3285.4, 3295.09, 3279.6, 3272.93,
3277.75, 3292.59, 3260, 3242.66, 3241.78, 3237.8, 3222.6, 3234.3, 3238.34,
3252.28, 3267.21, 3264.54, 3258.5, 3259.2, 3250.44, 3248.98, 3235.4,
3245.99, 3257.86, 3251.08, 3277.35, 3295.41, 3312.28, 3323.59, 3308.63,
3302.98, 3306.66, 3317.32, 3303.59, 3299.27, 3296.26, 3299, 3302.28,
3315.59, 3324.42, 3323.63, 3332.26, 3327.73, 3338.18, 3332.01, 3341.54,
3335.91, 3342.99, 3350.09, 3331.74, 3322.51, 3317.57, 3312.87, 3312.96,
3314.18, 3307.64, 3295.61, 3287, 3304.74, 3301.41, 3306.23, 3311.47,
3312.52, 3329.5, 3314.01, 3312.61, 3316.38, 3306.79, 3329.9, 3332.61,
3342.5, 3356, 3344.52, 3334.21, 3342.99, 3339.01, 3332.32, 3327.49,
3333.23, 3312.47, 3297.72, 3297.61, 3287.31, 3272.79, 3276.98, 3293.86,
3295.18, 3278.81, 3291.58, 3279.67, 3279.21, 3283.9, 3272.82, 3275.05,
3282.55, 3262.38, 3273.59, 3273.32, 3280.56, 3284.08, 3298.75, 3294.55,
3291.02, 3285.03, 3278.96, 3252.62, 3246.18, 3233.95, 3226.14, 3243.15,
3229.47, 3211.81, 3216.67, 3201.42, 3211.38, 3184.59, 3177.5, 3170.81,
3174.33, 3155.81, 3159.2, 3173.39, 3162.93, 3162.81, 3175.59, 3163.63,
3158.4, 3158.05, 3173.82, 3168.86, 3193.39, 3201.67, 3206.98, 3199.53,
3213.39, 3215.19, 3213.22, 3220.43, 3235.22, 3220.58, 3212.89, 3211.78,
3188.78, 3168.61, 3188.41, 3174.71, 3175.2, 3164.39, 3179.45, 3187.09,
3175.39, 3187.27, 3195.52, 3203.25, 3214.48, 3210.22, 3218.77, 3216.85,
3221, 3221.37, 3225.99, 3216.23, 3224, 3230.16, 3227.6, 3224.2, 3240,
3251.13, 3263.32, 3253.84, 3248.38, 3232.99, 3210.99, 3197.31, 3160.85,
3174.81, 3193.03, 3191.8, 3187.52, 3162.31, 3160.3, 3121.62, 3130,
3119.86, 3133, 3128.61, 3128.84, 3125.61, 3114.78, 3102.2, 3104.79,
3069.41, 3074, 3087.97, 3096.29, 3098.59, 3092.15, 3095.56, 3110.15,
3113.2, 3119.41, 3128.17, 3124.95, 3136.15, 3142.86, 3140, 3137.91,
3144.45, 3143.04, 3146.88, 3140.86, 3148.23, 3154.01, 3160.57, 3165.67,
3174.26, 3184.2, 3184.82, 3181.76, 3210.49, 3205.48, 3218.07, 3217.91,
3210.38, 3221.28, 3220.03, 3219.03, 3220.01, 3224.01, 3228.11, 3212.89,
3211.79, 3224.31, 3216.85, 3207.8, 3224.44, 3220.49, 3212.7, 3219.26,
];
const ethArrFile = fs.readFileSync('src/strategies/etharr.txt', 'utf8');
const etharr = JSON.parse(ethArrFile);
const { tradeArray } = await strategiesService.backTesting(
'autoArima',
'autoArima',
'autoArima',
ETHPriceArray,
etharr,
);
// Info: (20240320 - Jacky) this is aim to sum the profit of all trades
const profitArray = tradeArray.map((trade) => trade.profit);
const sum = profitArray.reduce((total, profit) => total + profit, 0);
expect(sum).toBe(-673.9419000000021);
expect(sum).toBe(-7023.318874999997);
});
});
12 changes: 12 additions & 0 deletions src/strategies/strategies.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Injectable } from '@nestjs/common';
import { Environment } from './strategy/rl-dqn/environment';
import { TradeAgent } from './strategy/rl-dqn/tradeAgent';
import { train } from './strategy/rl-dqn/train';
import * as fs from 'fs';

@Injectable()
export class StrategiesService {
Expand Down Expand Up @@ -93,4 +97,12 @@ export class StrategiesService {
}
return { tradeArray };
}

async trainDqn() {
const ethArrFile = fs.readFileSync('src/strategies/etharr.txt', 'utf8');
const etharr = JSON.parse(ethArrFile);
const env = new Environment(etharr);
const tradeAgent = new TradeAgent(env);
await train(tradeAgent);
}
}
2 changes: 1 addition & 1 deletion src/strategies/strategy/autoArima.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export function stopLoss(data: {
const AbsSpreadFee = Math.abs(data.spreadFee);
const openPrice = data.openPrice;
const currentPrice = data.currentPrice;
const stopLoss = AbsSpreadFee * 0.7;
const stopLoss = AbsSpreadFee * 1;
const holdingStatus = data.holdingStatus;
if (holdingStatus === 'BUY') {
// Info: (20240328 - Jacky) Current Buy price is lower than the open Buy price
Expand Down
2 changes: 1 addition & 1 deletion src/strategies/strategy/linear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function suggestion(data: {
let suggestion = 'WAIT';
const priceArray = data.priceArray;
const currentPrice = data.currentPrice;
const lastHourPrice = priceArray[priceArray.length - 11];
const lastHourPrice = priceArray[priceArray.length - 35];
if (currentPrice > lastHourPrice) {
suggestion = 'BUY';
}
Expand Down
62 changes: 62 additions & 0 deletions src/strategies/strategy/rl-dqn/dqn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* @license
* Copyright 2019 Google LLC. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =============================================================================
*/

import * as tf from '@tensorflow/tfjs-node';

export function createDeepQNetwork(numActions) {
const model = tf.sequential();
model.add(
tf.layers.dense({ units: 32, activation: 'relu', inputShape: [104] }),
);
model.add(tf.layers.dense({ units: 64, activation: 'relu' }));
model.add(tf.layers.dense({ units: 128, activation: 'relu' }));
model.add(tf.layers.dropout({ rate: 0.25 }));
// model.add(tf.layers.dense({ units: numActions }));
model.add(tf.layers.dense({ units: numActions, activation: 'sigmoid' }));
return model;
}

/**
* Copy the weights from a source deep-Q network to another.
*
* @param {tf.LayersModel} destNetwork The destination network of weight
* copying.
* @param {tf.LayersModel} srcNetwork The source network for weight copying.
*/
export function copyWeights(destNetwork, srcNetwork) {
// https://github.com/tensorflow/tfjs/issues/1807:
// Weight orders are inconsistent when the trainable attribute doesn't
// match between two `LayersModel`s. The following is a workaround.
// TODO(cais): Remove the workaround once the underlying issue is fixed.
let originalDestNetworkTrainable;
if (destNetwork.trainable !== srcNetwork.trainable) {
originalDestNetworkTrainable = destNetwork.trainable;
destNetwork.trainable = srcNetwork.trainable;
}

destNetwork.setWeights(srcNetwork.getWeights());

// Weight orders are inconsistent when the trainable attribute doesn't
// match between two `LayersModel`s. The following is a workaround.
// TODO(cais): Remove the workaround once the underlying issue is fixed.
// `originalDestNetworkTrainable` is null if and only if the `trainable`
// properties of the two LayersModel instances are the same to begin
// with, in which case nothing needs to be done below.
if (originalDestNetworkTrainable != null) {
destNetwork.trainable = originalDestNetworkTrainable;
}
}
127 changes: 127 additions & 0 deletions src/strategies/strategy/rl-dqn/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
export class Environment {
current_step: number;
initial_balance: number;
balance: number;
action_space: any;
done: boolean;
spreadFee: number;
holdingStatus: number;
priceArray: number[];
openPrice: number;
currentPrice: number;
profitLog: number[];
actionLog: any[];
rewardLog: any[];

constructor(priceArray) {
this.current_step = 48;
this.initial_balance = 10000;
this.spreadFee = 30;
this.balance = this.initial_balance;
this.priceArray = priceArray;
this.action_space = [0, 1, 2, 3]; // WAIT, BUY, SELL, CLOSE
this.done = false;
this.holdingStatus = 0;
this.profitLog = [];
this.actionLog = [];
this.rewardLog = [];
}

reset() {
this.current_step = 48;
this.initial_balance = 10000;
// this.spreadFee = 30;
this.balance = this.initial_balance;
this.done = false;
this.holdingStatus = 0;
this.profitLog = [];
return this;
}

// return {
// currentPrice: currentPrice,
// openPrice: this.openPrice,
// spreadFee: spreadFee,
// profit: profit,
// holdingStatus: 'WAIT',
// };
step(action: any) {
let profit = 0;
let reward = 0;
this.currentPrice = this.priceArray[this.current_step];
this.spreadFee = 0.005 * this.currentPrice;
if (action === 3) {
if (this.holdingStatus === 0) {
reward = -40;
}
if (this.holdingStatus === 1) {
profit = this.currentPrice - this.spreadFee - this.openPrice;
reward = profit;
reward += 10;
this.holdingStatus = 0;
}
if (this.holdingStatus === 2) {
profit = this.openPrice - (this.currentPrice + this.spreadFee);
reward = profit;
reward += 10;
this.holdingStatus = 0;
}
}
if (action === 1) {
if (this.holdingStatus === 0) {
const currentPriceStr = JSON.stringify(this.currentPrice);
this.openPrice = JSON.parse(currentPriceStr);
this.holdingStatus = 1;
reward = 25;
if (this.openPrice < this.priceArray[this.current_step + 10]) {
reward += 24;
} else {
reward += -14;
}
} else {
reward = -35;
}
}
if (action === 2) {
if (this.holdingStatus === 0) {
const currentPriceStr = JSON.stringify(this.currentPrice);
this.openPrice = JSON.parse(currentPriceStr);
this.holdingStatus = 2;
reward = 25;
if (this.openPrice > this.priceArray[this.current_step + 10]) {
reward += 20;
} else {
reward += -20;
}
} else {
reward = -35;
}
}
this.current_step += 1;
if (this.current_step >= this.priceArray.length) {
this.done = true;
}
if (action === 0) {
if (this.actionLog.slice(-24).every((a) => a === 0)) {
reward = -30;
}
}
return {
previousPrice: this.priceArray.slice(-100),
currentPrice: this.currentPrice,
openPrice: this.openPrice,
spreadFee: this.spreadFee,
holdingStatus: this.holdingStatus,
reward: reward,
};
}
getState() {
return [
this.priceArray.slice(-100),
this.currentPrice,
this.openPrice,
this.spreadFee,
this.holdingStatus,
];
}
}
1 change: 1 addition & 0 deletions src/strategies/strategy/rl-dqn/models/dqn/model.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"modelTopology":{"class_name":"Sequential","config":{"name":"sequential_1","layers":[{"class_name":"Dense","config":{"units":32,"activation":"relu","use_bias":true,"kernel_initializer":{"class_name":"VarianceScaling","config":{"scale":1,"mode":"fan_avg","distribution":"normal","seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense1","trainable":true,"batch_input_shape":[null,104],"dtype":"float32"}},{"class_name":"Dense","config":{"units":64,"activation":"relu","use_bias":true,"kernel_initializer":{"class_name":"VarianceScaling","config":{"scale":1,"mode":"fan_avg","distribution":"normal","seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense2","trainable":true}},{"class_name":"Dense","config":{"units":128,"activation":"relu","use_bias":true,"kernel_initializer":{"class_name":"VarianceScaling","config":{"scale":1,"mode":"fan_avg","distribution":"normal","seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense3","trainable":true}},{"class_name":"Dropout","config":{"rate":0.25,"noise_shape":null,"seed":null,"name":"dropout_Dropout1","trainable":true}},{"class_name":"Dense","config":{"units":4,"activation":"sigmoid","use_bias":true,"kernel_initializer":{"class_name":"VarianceScaling","config":{"scale":1,"mode":"fan_avg","distribution":"normal","seed":null}},"bias_initializer":{"class_name":"Zeros","config":{}},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null,"name":"dense_Dense4","trainable":true}}]},"keras_version":"tfjs-layers 4.17.0","backend":"tensor_flow.js"},"weightsManifest":[{"paths":["weights.bin"],"weights":[{"name":"dense_Dense1/kernel","shape":[104,32],"dtype":"float32"},{"name":"dense_Dense1/bias","shape":[32],"dtype":"float32"},{"name":"dense_Dense2/kernel","shape":[32,64],"dtype":"float32"},{"name":"dense_Dense2/bias","shape":[64],"dtype":"float32"},{"name":"dense_Dense3/kernel","shape":[64,128],"dtype":"float32"},{"name":"dense_Dense3/bias","shape":[128],"dtype":"float32"},{"name":"dense_Dense4/kernel","shape":[128,4],"dtype":"float32"},{"name":"dense_Dense4/bias","shape":[4],"dtype":"float32"}]}],"format":"layers-model","generatedBy":"TensorFlow.js tfjs-layers v4.17.0","convertedBy":null}
Binary file not shown.
Loading

0 comments on commit 116d08f

Please sign in to comment.