NapBots - Dynamic Allocations v1
@gahabeen
code:
data:privatelast updated:4 years ago
today
Build integrations remarkably fast!
You're viewing a public workflow template.
Sign up to customize, add steps, modify code and more.
Join 1,000,000+ developers using the Pipedream platform
steps.
trigger
Cron Scheduler
Deploy to configure a custom schedule
This workflow runs on Pipedream's servers and is triggered on a custom schedule.
steps.
setup
auth
to use OAuth tokens and API keys in code via theauths object
params
Optional
code
Write any Node.jscodeand use anynpm package. You can alsoexport datafor use in later steps via return or this.key = 'value', pass input data to your code viaparams, and maintain state across executions with$checkpoint.
async (event, steps, params) => {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
}
118

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
}
steps.
metadata
auth
to use OAuth tokens and API keys in code via theauths object
code
Write any Node.jscodeand use anynpm package. You can alsoexport datafor use in later steps via return or this.key = 'value', pass input data to your code viaparams, and maintain state across executions with$checkpoint.
async (event, steps) => {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
}
51
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())
}
steps.
allocator
auth
to use OAuth tokens and API keys in code via theauths object
code
Write any Node.jscodeand use anynpm package. You can alsoexport datafor use in later steps via return or this.key = 'value', pass input data to your code viaparams, and maintain state across executions with$checkpoint.
async (event, steps) => {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
}
326
/**
 * 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, 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;
  }

  // Rebuild string for composition
  const data = {
    botOnly: userExchangeCompositionToSet.botOnly,
    compo: {
      leverage: userExchangeCompositionToSet.leverage.toFixed(2),
      compo: userExchangeCompositionToSet.compo,
    },
  };

  // Make the updated data available to the other steps
  output.exchanges[userExchangeCode].data = data

  // If composition different, update allocation for this exchange
  if (output.exchanges[userExchangeCode].update) {
    
    // 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
steps.
triggers
auth
to use OAuth tokens and API keys in code via theauths object
code
Write any Node.jscodeand use anynpm package. You can alsoexport datafor use in later steps via return or this.key = 'value', pass input data to your code viaparams, and maintain state across executions with$checkpoint.
async (event, steps) => {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
}
63

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

for (let exchangeCode of Object.keys(output?.exchanges)) {
  const exchange = output?.exchanges?.[exchangeCode]
  hasUpdateOrFailure = hasUpdateOrFailure || exchange.update || !!exchange.error
}

// console.log('hasUpdateOrFailure', hasUpdateOrFailure)

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>

${Object.keys(output?.exchanges).map(exchangeCode => {
  const exchange = output?.exchanges?.[exchangeCode]
  return `
    <h2>${exchangeCode}</h2>
    <h3>Status:</h3>
    ${exchange.update ? 'Updated' : 'No update required'} [${exchange.update_reason || exchange.error || 'Success'}]
    <h3>Leverage</h3>
    ${exchange.data.compo.leverage}
    <h3>Bot only</h3>
    ${exchange.data.botOnly ? "Yes" : 'No'}
    <h3>Allocations</h3>
    <table style="text-align:left;">
      <thead>
        <tr>
          <th style="padding:5px 10px 5px 0;">Strategy</th>
          <th style="padding:5px 0 5px 10px;">Percentage</th>
        </tr>
      </thead>
      <tbody>
        ${Object.keys(exchange.data.compo.compo).map(stratCode => `
          <tr>
            <td style="padding:5px 10px 5px 0;">${stratCode}</td>
            <td style="padding:5px 0 5px 10px;">${exchange.data.compo.compo[stratCode] * 100}%</td>
          </tr>
        `).join('')}
      </tbody>
    </table>
  `
}).join('')}

<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>`
  });
}