Skip to content

Commit

Permalink
Added market.base and market.quote facts (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
srcmayte authored Apr 7, 2024
1 parent 54623fb commit 29be5ff
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 7 deletions.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,44 @@ Facts can be used in any of the following, recursively.
# Built-in facts
Strategy engine comes with some built in facts.

## Market base symbol: `market.base`
This fact provides the base symbol of `market`.

**Example**
```json
{
"fact": "market.base",
"params": {
"market": "BTC/USDT"
}
}
// Returns BTC
```

### Properties
Property | Default | Description
-------- | -------- | -----------
`market` | | The market to get the base symbol from.

## Market quote symbol: `market.quote`
This fact provides the quote symbol of `market`.

**Example**
```json
{
"fact": "market.quote",
"params": {
"market": "BTC/USDT"
}
}
// Returns USDT
```

### Properties
Property | Default | Description
-------- | -------- | -----------
`market` | | The market to get the quote symbol from.

## Exponential Moving Average: `ta.ema`
This fact provides the EMA of `values` for a `period`.

Expand Down
5 changes: 1 addition & 4 deletions lib/facts/base.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const _ = require('lodash')

const { ValidationError } = require('../errors')
const { validate } = require('../validator')

Expand All @@ -20,8 +18,7 @@ class Fact {
return {}
}

value (originalParams = {}) {
const params = _.cloneDeep(originalParams)
value (params = {}) {
this.#validateAndSanitize(params)

return params
Expand Down
3 changes: 2 additions & 1 deletion lib/facts/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module.exports = {
ta: require('./ta')
ta: require('./ta'),
market: require('./market')
}
30 changes: 30 additions & 0 deletions lib/facts/market/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const Fact = require('../base')

class Base extends Fact {
get id () {
return 'market.base'
}

get name () {
return 'Base symbol for market'
}

get description () {
return 'Get the base symbol from a market symbol'
}

get schema () {
return {
market: { type: 'market', required: true, description: 'The market symbol.' },
$$strict: 'remove'
}
}

async value (params = {}) {
params = super.value(params)

return params.market.split('/')[0]
}
}

module.exports = Base
4 changes: 4 additions & 0 deletions lib/facts/market/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const base = require('./base')
const quote = require('./quote')

module.exports = { base, quote }
30 changes: 30 additions & 0 deletions lib/facts/market/quote.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const Fact = require('../base')

class Quote extends Fact {
get id () {
return 'market.quote'
}

get name () {
return 'Quote symbol for market'
}

get description () {
return 'Get the quote symbol from a market symbol'
}

get schema () {
return {
market: { type: 'market', required: true, description: 'The market symbol.' },
$$strict: 'remove'
}
}

async value (params = {}) {
params = super.value(params)

return params.market.split('/')[1]
}
}

module.exports = Quote
26 changes: 24 additions & 2 deletions lib/strategyEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ class StrategyEngine {

opts.facts.forEach(fact => this.addFact(fact))

for (const name in facts.ta) {
const fact = new facts.ta[name]()
const factsArray = this.#factsToArray(facts)

for (const factConstructor of factsArray) {
const fact = new factConstructor() /* eslint-disable-line new-cap */

if (!this.engine.facts.has(fact.id)) {
this.addFact(fact)
Expand Down Expand Up @@ -131,6 +133,26 @@ class StrategyEngine {
return params
}

#factsToArray (facts = {}) {
const array = []
const keys = Object.keys(facts)

for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const value = facts[key]

if (typeof value === 'function') {
if (!value.toString().toLowerCase().includes('backtest')) {
array.push(value)
}
} else {
array.push(...this.#factsToArray(value))
}
}

return array
}

#validateAndSanitizeOpts (opts) {
const valid = validate(opts, {
rules: { type: 'array', items: 'object', optional: true, default: [], convert: false },
Expand Down
44 changes: 44 additions & 0 deletions test/facts/market/base.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { expect, chance } = require('../../helpers')
const Base = require('../../../lib/facts/market/base')

describe('Fact market.base', () => {
describe('#value', () => {
const fact = new Base()

describe('params', () => {
describe('market', () => {
it('is required', async () => {
let thrownErr = null

try {
await fact.value()
} catch (err) {
thrownErr = err
}

expect(thrownErr.type).to.eql('VALIDATION_ERROR')
expect(thrownErr.data[0].message).to.eql('market is required')
})

it('must be a string', async () => {
let thrownErr = null

try {
await fact.value({ market: chance.bool() })
} catch (err) {
thrownErr = err
}

expect(thrownErr.type).to.eql('VALIDATION_ERROR')
expect(thrownErr.data[0].message).to.eql('market must be a string')
})
})
})

it('returns the base symbol', async () => {
const result = await fact.value({ market: 'BTC/USD' })

expect(result).to.eql('BTC')
})
})
})
44 changes: 44 additions & 0 deletions test/facts/market/quote.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { expect, chance } = require('../../helpers')
const Quote = require('../../../lib/facts/market/quote')

describe('Fact market.quote', () => {
describe('#value', () => {
const fact = new Quote()

describe('params', () => {
describe('market', () => {
it('is required', async () => {
let thrownErr = null

try {
await fact.value()
} catch (err) {
thrownErr = err
}

expect(thrownErr.type).to.eql('VALIDATION_ERROR')
expect(thrownErr.data[0].message).to.eql('market is required')
})

it('must be a string', async () => {
let thrownErr = null

try {
await fact.value({ market: chance.bool() })
} catch (err) {
thrownErr = err
}

expect(thrownErr.type).to.eql('VALIDATION_ERROR')
expect(thrownErr.data[0].message).to.eql('market must be a string')
})
})
})

it('returns the quote symbol', async () => {
const result = await fact.value({ market: 'BTC/USDT' })

expect(result).to.eql('USDT')
})
})
})
12 changes: 12 additions & 0 deletions test/strategyEngine.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,18 @@ describe('StrategyEngine', () => {
await expect(engine.engine.facts.get('ta.sma').calculationMethod()).to.eventually.eql(true)
})

it('has market.base', () => {
const engine = new StrategyEngine()

expect(engine.engine.facts.has('market.base')).to.eql(true)
})

it('has market.quote', () => {
const engine = new StrategyEngine()

expect(engine.engine.facts.has('market.quote')).to.eql(true)
})

it('has ta.sma', () => {
const engine = new StrategyEngine()

Expand Down

0 comments on commit 29be5ff

Please sign in to comment.