Skip to content

Instantly share code, notes, and snippets.

@gahabeen
Last active March 13, 2021 14:45
Show Gist options
  • Save gahabeen/5e8bd764466cc4a554523a6247f414fb to your computer and use it in GitHub Desktop.
Save gahabeen/5e8bd764466cc4a554523a6247f414fb to your computer and use it in GitHub Desktop.
NapBots - Dynamic Allocations v1
For updates, leave a comment below!
const compositions = {
// ******************
// MODIFY BELOW ONLY
//
BINANCE: {
mild_bear: {
compo: {
STRAT_BTC_USD_FUNDING_8H_1: 0.15,
STRAT_ETH_USD_FUNDING_8H_1: 0.15,
STRAT_BTC_ETH_USD_H_1: 0.7,
},
leverage: 1.0,
botOnly: true,
},
mild_bull: {
compo: {
STRAT_BTC_USD_FUNDING_8H_1: 0.25,
STRAT_ETH_USD_FUNDING_8H_1: 0.25,
STRAT_BTC_ETH_USD_H_1: 0.5,
},
leverage: 1.5,
botOnly: true,
},
extreme: {
compo: {
STRAT_ETH_USD_H_3_V2: 0.4,
STRAT_BTC_USD_H_3_V2: 0.4,
STRAT_BTC_ETH_USD_H_1: 0.2,
},
leverage: 1.0,
botOnly: true,
},
},
BITFINEX: {
mild_bear: {
compo: {
STRAT_BTC_USD_FUNDING_8H_1: 0.15,
STRAT_ETH_USD_FUNDING_8H_1: 0.15,
STRAT_BTC_ETH_USD_H_1: 0.7,
},
leverage: 1.0,
botOnly: true,
},
mild_bull: {
compo: {
STRAT_BTC_USD_FUNDING_8H_1: 0.25,
STRAT_ETH_USD_FUNDING_8H_1: 0.25,
STRAT_BTC_ETH_USD_H_1: 0.5,
},
leverage: 1.5,
botOnly: true,
},
extreme: {
compo: {
STRAT_ETH_USD_H_3_V2: 0.4,
STRAT_BTC_USD_H_3_V2: 0.4,
STRAT_BTC_ETH_USD_H_1: 0.2,
},
leverage: 1.0,
botOnly: true,
},
},
/**
* Remove the double-slashes // in front of your exchange to start editing it.
* You may as well copy/paste the configuration from one of the examples above to start with.
*/
// BITSTAMP: {},
// KRAKEN: {},
// BITMEX: {},
// PHEMEX: {},
// OKEX: {},
// BITPANDA: {}
//
// MODIFY ABOVE ONLY
// ******************
}
/**
* See below all available strategies labels and their respective codes
* NapoX BCH LO daily (STRAT_BCH_USD_LO_D_1)
* NapoX medium term TF BTC LO (STRAT_BTC_USD_D_3)
* NapoX medium term TF EOS LO (STRAT_EOS_USD_D_2)
* NapoX medium term TF ETH LO (STRAT_ETH_USD_D_3)
* NapoX medium term TF LTC LO (STRAT_LTC_USD_D_1)
* NapoX medium term TF XRP LO (STRAT_XRP_USD_D_1)
* NapoX alloc ETH/BTC/USD AR hourly (STRAT_BTC_ETH_USD_H_1)
* NapoX BNB LO daily (STRAT_BNB_USD_LO_D_1)
* NapoX alloc ETH/BTC/USD LO daily (STRAT_BTC_ETH_USD_LO_D_1)
* NapoX alloc ETH/BTC/USD LO hourly (STRAT_BTC_ETH_USD_LO_H_1)
* NapoX ETH Volume AR daily (STRAT_ETH_USD_VOLUME_H_1)
* NapoX BTC Volume AR daily (STRAT_BTC_USD_VOLUME_H_1)
* NapoX BTC Funding AR hourly (STRAT_BTC_USD_FUNDING_8H_1)
* NapoX ETH Funding AR hourly (STRAT_ETH_USD_FUNDING_8H_1)
* NapoX ETH Ultra flex AR hourly (STRAT_ETH_USD_H_3_V2)
* NapoX BTC Ultra flex AR hourly (STRAT_BTC_USD_H_3_V2)
* NapoX alloc ETH/BTC/USD AR daily (STRAT_BTC_ETH_USD_D_1_V2)
* NapoX BTC AR daily (STRAT_BTC_USD_D_2_V2)
* NapoX ETH AR daily (STRAT_ETH_USD_D_2_V2)
* NapoX ETH AR hourly (STRAT_ETH_USD_H_4_V2)
* NapoX BTC AR hourly (STRAT_BTC_USD_H_4_V2)
*/
// Dynamic params that yous set in the "params" section above the code section
return {
email: params.NAPBOTS_EMAIL,
password: params.NAPBOTS_PASSWORD,
// testMode: params.TEST_MODE,
compositions
}
const axios = require('axios');
// Axios wrapper to easily handle response/error
const ask = async (options) => axios(options).then(({ data }) => ({ success: true, ...data })).catch(err => {
console.error(err)
return { success: false }
})
if (!$checkpoint) $checkpoint = { metadata: {}, users: {} }
if (!$checkpoint.metadata) $checkpoint.metadata = {}
if (!$checkpoint.users) $checkpoint.users = {}
const ONE_DAY = 24 * 60 * 60 * 1000
const cacheHasExpired = !$checkpoint.metadata.cachedAt || ($checkpoint.metadata.cachedAt + ONE_DAY) < new Date().getTime()
console.log('cacheHasExpired', cacheHasExpired)
if (cacheHasExpired) {
// Set cached timestamp
$checkpoint.metadata.cachedAt = new Date().getTime()
// Update exchanges
const exchangesResponse = await ask({ url: 'https://middle.napbots.com/v1/exchange' })
if (exchangesResponse.success) {
$checkpoint.metadata.exchanges = (exchangesResponse.data ?? []).reduce((acc, exchange) => {
acc[exchange.code] = exchange
return acc
}, {})
}
// Update strategies by exchanges
for (let exchangeKey of Object.keys($checkpoint.metadata.exchanges)) {
const strategiesResponse = await ask({ url: 'https://middle.napbots.com/v1/exchange/available-strategies/' + exchangeKey })
if (strategiesResponse.success) {
$checkpoint.metadata.exchanges[exchangeKey].strategies = (strategiesResponse.data ?? []).reduce((acc, strategy) => {
acc[strategy.code] = strategy
return acc
}, {})
$checkpoint.metadata.exchanges[exchangeKey].strategiesRecap = (strategiesResponse.data ?? []).reduce((text, strategy) => {
return text + `${strategy.label} (${strategy.code}) \n`
}, "")
}
}
} else {
console.log("Using $checkpoint cached at ", new Date($checkpoint.metadata.cachedAt).toISOString())
}
/**
* Based on https://github.com/PierrickI3/napbots by @PierrickI3
* Which is originally based on a gist from @julienarcin (https://gist.github.com/julienarcin/af2727307de2fd37d6a72973eafdbfc9)
*/
const axios = require('axios');
const deepEqual = require('lodash.isequal')
// Axios wrapper to easily handle response/error
const ask = async (options) => axios(options).then(({ data }) => ({ success: true, ...data })).catch(err => {
console.error(err)
return { success: false }
})
// Set the params in the previous code editor
const { email, password, testMode, compositions } = steps.setup.$return_value
let output = {
exchanges: {}
}
// Fetches NAPBOTS METADATA (DAILY CACHED LAYER)
const metadata = $checkpoint.metadata
const WEATHER_INDICATOR = {
extreme: "Extreme markets",
mild_bull: "Mild bull markets",
mild_bear: "Mild bear or range markets"
}
const endpoints = {
LOGIN: 'https://middle.napbots.com/v1/user/login',
ME: 'https://middle.napbots.com/v1/user/me',
CRYPTO_WEATHER: 'https://middle.napbots.com/v1/crypto-weather',
ACCOUNT: 'https://middle.napbots.com/v1/account/for-user/'
}
// Check NapBots Availibility
const available = await ask({ url: endpoints.CRYPTO_WEATHER })
if (!available.success) {
// IF NAPBOTS seem OFF, STOP
// Could be because of a maintenance
const errorMsg = `NapBots API needs to be available to go any further.`
console.error(errorMsg)
output.error = errorMsg
}
const checkStrategyByExchange = (exchangeCode, strategyCode, strategy) => {
const exchange = metadata.exchanges[exchangeCode]
const availableStrategiesCodes = Object.keys(metadata.exchanges[exchangeCode].strategies)
// Check composition
const composition = strategy?.compo
const compositionCodes = Object.keys(composition)
const unavailableStrategiesCodes = compositionCodes.filter(code => !availableStrategiesCodes.includes(code))
// console.log('availableStrategiesCodes', availableStrategiesCodes)
// console.log('compositionCodes', compositionCodes)
// console.log('unavailableStrategiesCodes', unavailableStrategiesCodes)
if (unavailableStrategiesCodes.length > 0) {
const errorMsg = `It seems like your using unavailable strategies (${unavailableStrategiesCodes.join(', ')}) for the exchange ${exchangeCode}.`
console.error(errorMsg)
output.error = errorMsg
return false
}
// Check composition percentage
const compositionPercentage = +Object.values(composition).reduce((total, percentage) => total + percentage.toFixed(2), 0).toFixed(2)
if (compositionPercentage > 1) {
const errorMsg = `The percentage for your strategy ${strategyCode} in ${exchangeCode} is higher than 100% (${compositionPercentage * 100}%)`
console.error(errorMsg)
output.error = errorMsg
return false
}
// Check leverage
const validLeverage = strategy.leverage.toFixed(2) <= exchange.maxLeverage
if (!validLeverage) {
const errorMsg = `The leverage for your strategy ${strategyCode} in ${exchangeCode} is higher than ${strategy.leverage * 100}% (${exchange.maxLeverage * 100}%)`
console.error(errorMsg)
output.error = errorMsg
return false
}
return true
}
const compositionsAreValid = Object.keys(compositions).reduce((valid, exchange) => {
const strategiesCodes = Object.keys(compositions[exchange])
const containsRequiredStrategies = Object.keys(WEATHER_INDICATOR).every(code => strategiesCodes.includes(code))
const missingRequiredStrategies = Object.keys(WEATHER_INDICATOR).filter(code => !strategiesCodes.includes(code))
if (!containsRequiredStrategies) {
console.error(`Following strategies are missing for ${exchange}: ${missingRequiredStrategies.join(', ')}`)
}
const strategiesValidated = strategiesCodes.every(strategyCode => checkStrategyByExchange(exchange, strategyCode, compositions[exchange][strategyCode]))
return valid && containsRequiredStrategies && strategiesValidated
}, true)
if (!compositionsAreValid) {
// IF INVALID COMPOSITION, STOP
const errorMsg = `Valid compositions are required to go any further.`
console.error(errorMsg)
output.error = errorMsg
return output
}
const getCryptoWeater = async () => {
const { success, data } = await ask({
url: endpoints.CRYPTO_WEATHER
});
if (!success) {
console.error('No weather information found.');
return;
}
console.log('Current weather:', data?.weather?.weather);
return data?.weather?.weather;
};
const getAuthToken = async () => {
let { success, data } = await ask({
url: endpoints.LOGIN,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: {
email,
password,
},
})
const authToken = data?.accessToken
if (!success || !authToken) {
console.error('No Auth Token');
return;
}
console.log(`Logged in`)
return authToken
};
const getUserId = async (token) => {
if ($checkpoint.users?.[email]?.id) {
return $checkpoint.users?.[email]?.id
}
if (!$checkpoint.users?.[email]) {
$checkpoint.users[email] = { id: null }
}
const { success, data } = await ask({ url: endpoints.ME, headers: { 'Host': 'middle.napbots.com', token, } })
const userId = data?.userId
if (!success || !userId) {
console.error('No User ID')
return
}
$checkpoint.users[email].id = userId
return userId
}
const getCurrentAllocations = async (authToken) => {
let { success, data } = await ask({
url: endpoints.ACCOUNT + userId,
method: 'GET',
headers: {
'Content-Type': 'application/json',
token: authToken,
},
});
if (!success) {
console.error(`Couldn't get the current bot allocations`);
return;
}
const currentAllocations = data ?? []
// console.log('currentAllocations', currentAllocations)
// Rebuild exchanges array
let exchanges = [];
for (let currentAllocation of currentAllocations) {
if (!currentAllocation.accountId || !currentAllocation.compo || !currentAllocation.tradingActive) {
continue; // Next
}
exchanges.push({
id: currentAllocation.accountId,
code: currentAllocation.exchange,
compo: currentAllocation.compo,
});
}
console.log(`Current allocations by exchange:`, exchanges)
return exchanges;
};
const weather = await getCryptoWeater();
if (!weather) {
// IF NO WEATHER FOUND, STOP
// Could be because of a maintenance of the website or a modification of the API
const errorMsg = `Weather informations are required to go any further.`
console.error(errorMsg)
output.error = errorMsg
return output
}
let compositionToSet = {}
for (let exchangeCode of Object.keys(compositions)) {
switch (weather) {
case WEATHER_INDICATOR.extreme:
compositionToSet[exchangeCode] = compositions[exchangeCode].extreme;
break;
case WEATHER_INDICATOR.mild_bull:
compositionToSet[exchangeCode] = compositions[exchangeCode].mild_bull;
break;
case WEATHER_INDICATOR.mild_bear:
compositionToSet[exchangeCode] = compositions[exchangeCode].mild_bear;
break;
default:
console.error('Unknown weather condition:', weather);
return;
}
}
// console.log('compositionToSet', compositionToSet)
console.log('Authenticating...');
const authToken = await getAuthToken();
if (!authToken) {
// IF LOGIN FAILED, STOP
// Could be because of a typo in your login/password or a modification in the API
const errorMsg = `Authentication is required to go any further.`
console.error(errorMsg)
output.error = errorMsg
return output
}
const userId = await getUserId(authToken);
if (!userId) {
// IF User isn't accessible, STOP
const errorMsg = `User data required to be available to go any further.`
console.error(errorMsg)
output.error = errorMsg
return output
}
const userExchanges = await getCurrentAllocations(authToken);
if (!Array.isArray(userExchanges)) return
// For each exchange, update allocation if different from the current crypto weather
for (let userExchange of userExchanges) {
const userExchangeCode = userExchange.code
output.exchanges[userExchangeCode] = {}
const userExchangeCompositionToSet = compositionToSet[userExchangeCode]
output.exchanges[userExchangeCode].update = false;
// If leverage different, set to update
if (userExchange.compo.leverage.toFixed(2) !== userExchangeCompositionToSet.leverage.toFixed(2)) {
console.log('=> Leverage is different');
output.exchanges[userExchangeCode].update_reason = "Updating because leverage is different"
output.exchanges[userExchangeCode].update = true;
}
// If composition different, set to update
let equalCompos = deepEqual(userExchange.compo.compo, userExchangeCompositionToSet.compo);
if (!equalCompos) {
console.log('=> Compositions are different');
output.exchanges[userExchangeCode].update_reason = "Updating because compositions are different"
output.exchanges[userExchangeCode].update = true;
}
// If composition different, update allocation for this exchange
if (output.exchanges[userExchangeCode].update) {
// Rebuild string for composition
const data = {
botOnly: userExchangeCompositionToSet.botOnly,
compo: {
leverage: userExchangeCompositionToSet.leverage.toFixed(2).toString(),
compo: userExchangeCompositionToSet.compo,
},
};
// Uncomment to activate the test mode feature
// if (!testMode) {
console.log('Updating allocation to:', data, 'for', userExchange);
const { success } = await ask({
url: 'https://middle.napbots.com/v1/account/' + userExchange.id,
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
token: authToken,
},
data,
});
if (success) {
console.log('Success!');
output.exchanges[userExchangeCode].update_success = true
} else {
const errorMsg = `Couldn't update the allocation. Check out the logs.`
console.error(errorMsg)
output.exchanges[userExchangeCode].error = errorMsg
output.exchanges[userExchangeCode].update_success = false
}
// } else {
// console.log("[Test Mode] Updating allocation to:", data, 'for', userExchange)
// }
} else {
console.log('No updates are necessary.');
}
}
return output
const output = steps.allocator.$return_value
if (output.error) {
$send.email({
subject: "[Error] Napbots - Dynamic Allocations",
html: `<p>The dynamic allocation failed before to be able to try to update it with the following error: <b>"${output.error}"</b></p>
<p><a href="https://pipedream.com/@${steps.trigger.context.owner_id}/${steps.trigger.context.workflow_id}">Check out your logs here.</a></p>
<br><br><br><br><br>`
});
}
let hasUpdateOrFailure = false
let statuses = []
for (let exchangeCode of Object.keys(output?.exchanges)) {
const exchange = output?.exchanges?.[exchangeCode]
hasUpdateOrFailure = hasUpdateOrFailure || exchange.update || !!exchange.error
statuses.push(`${exchangeCode} (${exchange.update ? 'updated' : 'no update required'}) - ${exchange.update_reason || exchange.error || 'Success'}`)
}
// console.log('hasUpdateOrFailure', hasUpdateOrFailure)
// console.log('statuses', statuses)
if (hasUpdateOrFailure) {
$send.email({
subject: "[Update] Napbots - Dynamic Allocations",
html: `<p>The weather forecast implied an update of your napbots.</p>
<p>Here follows your report:</p>
<ul>
${statuses.map(status => `<li><b>${status}</b></li>`)}
</ul>
<p><a href="https://pipedream.com/@${steps.trigger.context.owner_id}/${steps.trigger.context.workflow_id}">Check out your logs here.</a></p>
<br><br><br><br><br>`
});
}
@nicolaschenet
Copy link

J'ai inclus mes modifs sur mon fork: https://gist.github.com/nicolaschenet/a79f7899b0293bfff7d011524a530a2a

  • toFixed
  • Mail template

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment