Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #76

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0332a20
refactoring: simplify open positions check
arrabyte Jul 17, 2023
53fa358
app: add dca limit orders to average entry price
arrabyte Jul 19, 2023
2d6f8d9
scalp: increment openPosition count to handle burst of liquidations a…
arrabyte Jul 20, 2023
67717bd
app: fix undefined tikSize handling
arrabyte Jul 20, 2023
1f21d84
app: fix handle undefine close_type when receiving update message fro…
arrabyte Jul 20, 2023
f7a27e2
feature: add a paused list to manually set tp on paused tokens
arrabyte Aug 9, 2023
a25ed1c
order: add generic params to limit orders method
arrabyte Aug 9, 2023
5ffcf7c
app: hanlde DCA count on DCA_AVERAGE_ENTRIES enabled
arrabyte Aug 10, 2023
6b9f3d6
app: simplify trade_info._liquidity_trigger log
arrabyte Aug 10, 2023
6439d3d
app: major fix settings update
arrabyte Aug 13, 2023
bbe02a5
cache exchange calls
arrabyte Aug 18, 2023
fe2aa3a
feature: testnet to handle orders
arrabyte Aug 18, 2023
69977d6
trace trades as json format
arrabyte Aug 19, 2023
be62e19
fix missing use_dca_feature on closeOrphanOrders
arrabyte Aug 19, 2023
3371599
trades history: store roi
arrabyte Aug 21, 2023
c10862a
dump liquidations data in a separate file
arrabyte Aug 30, 2023
cb5837f
App: open positions side balanced
arrabyte Sep 22, 2023
d77e1d2
app: fix orphan orders handling
arrabyte Sep 27, 2023
fcce816
Trace Position: fix missing fields
arrabyte Sep 27, 2023
b235222
App: remove settings files from repo
arrabyte Sep 28, 2023
6734df9
fix start sequence initializing settings
arrabyte Oct 7, 2023
c42223f
filters: add filters to exclude some pairs from trading
arrabyte Oct 8, 2023
bbdb3d6
filters: add volatility filters
arrabyte Oct 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions account.json

This file was deleted.

685 changes: 412 additions & 273 deletions app.js

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion example.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
API_KEY = apikeyhere
API_SECRET = apisecrethere
API_KEY_WEBSOCKET = NONE
API_SECRET_WEBSOCKET = NONE
USE_TESTNET= false # testnet allows to place order on testnet but liquidation data require a real api key
LIQ_SOURCE = bybit
GUI_PASSWORD = password
GUI_SESSION_PASSWORD = secret
Expand Down Expand Up @@ -43,6 +46,15 @@ CHECK_FOR_UPDATE = false
TIMEOUT_BLACKLIST_FOR_BIG_DRAWDOWN = false
DRAWDOWN_THRESHOLD = 3.5
TRACE_TRADES = OFF # verbosity: OFF, ON, MAX
TRACE_TRADES_FIELDS = symbol, size, side, sizeUSD, pnl, liq, price, stop_loss, take_profit, fee, _max_loss, _liquidity_trigger, _dca_count, _start_price, _start_time, _end_time
TRACE_TRADES_FIELDS = symbol, size, side, sizeUSD, pnl, liq, price, stop_loss, take_profit, fee, _max_loss, _liquidity_trigger, _dca_count, _start_price, _averaged_price, _start_time, _update_time, _roi
PLACE_ORDERS_SEQUENTIALLY = false
LOG_LEVEL = INFO
DCA_TYPE = DCA_LIQUIDATIONS #add size on new liquidations. Instead DCA_AVERAGE_ENTRIES add limits orders (read doc).
DCA_SAFETY_ORDERS = 2 #WORK WITH DCA_AVERAGE_ENTRIES
DCA_VOLUME_SCALE = 1.3 #WORK WITH DCA_AVERAGE_ENTRIES
DCA_PRICE_DEVIATION_PRC = 2 #WORK WITH DCA_AVERAGE_ENTRIES
PAUSED_LIST = NOTHING #Add active trades that you want to manually set a take profit
TRADE_POSITIONS_SIDE_BALANCE = false # keeps the number of open long and short positions balanced
FILTER_MIN_LISTING_DAYS=30
FILTER_CHECK_VOLATILITY_PRC = 0.0 # max allowed volatility 20% - 0 to disable filter
FILTER_CHECK_VOLATILITY_PERIOD = 10 # calc volatility over period of days
130 changes: 130 additions & 0 deletions exchange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { logIT, LOG_LEVEL } from './log.js';
import chalk from 'chalk';

var fetch_i = 0;


const baseRateLimit = 2000;

const checkResponse = (resp, latestRateLimit = baseRateLimit) => {
let rateLimit = undefined;

if (resp.ret_msg != "OK") {
return [false, undefined];
}

//check rate_limit_status
if (resp.rate_limit_status) {
//check rate_limit_status
if (resp.rate_limit_status > 100) {
rateLimit = baseRateLimit;
logIT("Rate limit status: " + chalk.green(resp.rate_limit_status));
}
else if (resp.rate_limit_status > 75) {
rateLimit = latestRateLimit + 500;
logIT("Rate limit status: " + chalk.greenBright(resp.rate_limit_status));
}
else if (resp.rate_limit_status > 50) {
rateLimit = latestRateLimit + 1000;
logIT("Rate limit status: " + chalk.yellowBright(resp.rate_limit_status));
}
else if (resp.rate_limit_status > 25) {
rateLimit = latestRateLimit + 2000;
logIT("Rate limit status: " + chalk.yellow(resp.rate_limit_status));
}
else {
rateLimit = latestRateLimit + 4000;
logIT("Rate limit status: " + chalk.red(resp.rate_limit_status));
}
}
return [true, rateLimit];
}

export class CachedLinearClient {
constructor(linearClient) {
this.linearClient = linearClient;
this.walletBalance = {res: undefined, invalidated: true};
this.positions = {res: undefined, invalidated: true};
this.tickers = {res: undefined, invalidated: true};
this.rateLimit = baseRateLimit;
}

async getWalletBalance() {
let rate_limit = undefined;
if (this.walletBalance.invalidated) {
//let rate_limit = undefined;
this.walletBalance.res = await this.linearClient.getWalletBalance();
const [res, rate_limit ] = checkResponse(this.walletBalance.res, this.rateLimit);
if (!res) {
throw Error(`CachedLinearClient::getWalletBalance fail err: ${this.walletBalance.res.ret_msg}`);
}
this.walletBalance.invalidated = false;
if (rate_limit) {
this.rateLimit = rate_limit;
}
}

return {
'available_balance': this.walletBalance.res.result['USDT'].available_balance,
'used_margin': this.walletBalance.res.result['USDT'].used_margin,
'whole_balance': this.walletBalance.res.result['USDT'].available_balance + this.walletBalance.res.result['USDT'].used_margin
};
}

async getPosition() {
let rate_limit = undefined;
if (this.positions.invalidated) {
//let rate_limit = undefined;
this.positions.res = await this.linearClient.getPosition();
const [res, rate_limit ] = checkResponse(this.positions.res, this.rateLimit);
if (!res) {
throw Error(`CachedLinearClient::getPosition fail err: ${this.positions.res.ret_msg}`);
}
this.positions.invalidated = false;
if (rate_limit) {
this.rateLimit = rate_limit;
}
}
return this.positions.res;
}

async getOpenPositions() {
const positions = await this.getPosition();
const openPositions = positions.result.filter(el => el.data.size > 0).length;
return openPositions;
}

async getTickers(symbolObj = undefined) {
if (this.tickers.invalidated) {
this.tickers.res = await this.linearClient.getTickers();
const [res] = checkResponse(this.tickers.res, this.rateLimit);
if (!res) {
throw Error(`CachedLinearClient::getTickers fail err: ${this.tickers.res.ret_msg}`);
}

this.tickers.invalidated = false;
}

let ticker = {result: []};
if (symbolObj) {
const t = this.tickers.res.result.find(el => el.symbol == symbolObj.symbol);
if (t == undefined) {
throw Error(`CachedLinearClient::getTickers fail symbol not found: ${symbolObj.symbol}`);
} else {
return {result: [t]}
}
}

return symbolObj ? ticker : this.tickers.res;
}

getRateLimit() {
return this.rateLimit;
}

invalidate(){
this.walletBalance.invalidated = true;
this.positions.invalidated = true;
this.tickers.invalidated = true;
}
}
44 changes: 44 additions & 0 deletions filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import fetch from 'node-fetch';
import { logIT, LOG_LEVEL } from './log.js';

export async function checkListingDate(symbol, days) {
let res = false;
try {
const today = new Date(); // Current date and time
today.setHours(0, 0, 0, 0); // Set the time to 00:00

const startDaysAgo = new Date(today);
const endDaysAgo = new Date(today);
startDaysAgo.setDate(today.getDate() - days);
endDaysAgo.setDate(today.getDate() - (days - 1));
const startTimestamp = startDaysAgo.getTime();
const endTimestamp = endDaysAgo.getTime();

const url = `https://api.bybit.com/v5/market/kline?category=linear&symbol=${symbol}&interval=D&start=${startTimestamp}&end=${endTimestamp}`;

const response = await fetch(url);
const data = await response.json();
res = data.result.list.length > 0;
} catch (error) {
console.error('Error:', error.message);
throw error;
}
return res;
}

export async function getVolatility(symbol, days) {
let vol = 0;
try {
const url = `https://api.bybit.com/v5/market/kline?category=linear&symbol=${symbol}&interval=D&limit=${days}`;
const response = await fetch(url);
const data = await response.json();
const historicalData = data.result.list;

// data are in format ts, ohlcv
vol = Math.max(...historicalData.map(item => (Number(item[2]) - Number(item[3])) / Number(item[3])));
} catch (error) {
throw error;
}

return vol;
}
Loading