diff --git a/src/models/Strategy.js b/src/models/Strategy.js index 6f78bd348..e8116b208 100644 --- a/src/models/Strategy.js +++ b/src/models/Strategy.js @@ -1,99 +1,24 @@ import mongoose, { Schema } from 'mongoose'; -const strategySchema = new Schema({ - fbId: { - type: String, - required: true, - unique: true, - dropDups: true, - }, - subStrategies: [{ - name: { +const strategySchema = new Schema( + { + fbSlug: { type: String, - default: '', + required: true, + unique: true, + dropDups: true, }, - entryPoint: { - situations: [{ - name: { - type: String, - default: '', - }, - conditions: [{ - name: { - type: String, - default: '', - }, - code: { - type: String, - default: 'false', - }, - }], - }], - }, - exitPoint: { - situations: [{ - name: { - type: String, - default: '', - }, - conditions: [{ - name: { - type: String, - default: '', - }, - code: { - type: String, - default: 'false', - }, - }], - }], - }, - sellPoint: { - situations: [{ - name: { - type: String, - default: '', - }, - conditions: [{ - name: { - type: String, - default: '', - }, - code: { - type: String, - default: 'false', - }, - }], - }], - }, - buyPoint: { - situations: [{ - name: { - type: String, - default: '', - }, - conditions: [{ - name: { - type: String, - default: '', - }, - code: { - type: String, - default: 'false', - }, - }], - }], - }, - stopLoss: { - phases: [{ - name: { - type: String, - default: '', - }, - code: { - type: String, - default: 'false', - }, + subStrategies: [{ + active: { + type: Boolean, + required: true, + default: true, + }, + name: { + type: String, + default: '', + }, + entryPoint: { situations: [{ name: { type: String, @@ -110,18 +35,8 @@ const strategySchema = new Schema({ }, }], }], - }], - }, - buyOrder: { - phases: [{ - name: { - type: String, - default: '', - }, - code: { - type: String, - default: 'false', - }, + }, + exitPoint: { situations: [{ name: { type: String, @@ -138,18 +53,8 @@ const strategySchema = new Schema({ }, }], }], - }], - }, - sellOrder: { - phases: [{ - name: { - type: String, - default: '', - }, - code: { - type: String, - default: 'false', - }, + }, + sellPoint: { situations: [{ name: { type: String, @@ -166,10 +71,280 @@ const strategySchema = new Schema({ }, }], }], + }, + buyPoint: { + situations: [{ + name: { + type: String, + default: '', + }, + conditions: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + }], + }], + }, + stopLoss: { + phases: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + situations: [{ + name: { + type: String, + default: '', + }, + conditions: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + }], + }], + }], + }, + buyOrder: { + phases: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + situations: [{ + name: { + type: String, + default: '', + }, + conditions: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + }], + }], + }], + }, + sellOrder: { + phases: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + situations: [{ + name: { + type: String, + default: '', + }, + conditions: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + }], + }], + }], + }, + }], + history: [{ + updatedAt: Date, + subStrategies: [{ + name: { + type: String, + default: '', + }, + entryPoint: { + situations: [{ + name: { + type: String, + default: '', + }, + conditions: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + }], + }], + }, + exitPoint: { + situations: [{ + name: { + type: String, + default: '', + }, + conditions: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + }], + }], + }, + sellPoint: { + situations: [{ + name: { + type: String, + default: '', + }, + conditions: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + }], + }], + }, + buyPoint: { + situations: [{ + name: { + type: String, + default: '', + }, + conditions: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + }], + }], + }, + stopLoss: { + phases: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + situations: [{ + name: { + type: String, + default: '', + }, + conditions: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + }], + }], + }], + }, + buyOrder: { + phases: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + situations: [{ + name: { + type: String, + default: '', + }, + conditions: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + }], + }], + }], + }, + sellOrder: { + phases: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + situations: [{ + name: { + type: String, + default: '', + }, + conditions: [{ + name: { + type: String, + default: '', + }, + code: { + type: String, + default: 'false', + }, + }], + }], + }], + }, }], - }, - }], -}); + }], + }, + { + timestamps: true, + }, +); const Strategy = mongoose.model('Strategy', strategySchema); diff --git a/src/schema/mutations/CreateStrategy.js b/src/schema/mutations/CreateStrategy.js index 7a0bfe720..0ab2216a9 100644 --- a/src/schema/mutations/CreateStrategy.js +++ b/src/schema/mutations/CreateStrategy.js @@ -1,12 +1,22 @@ +import { + GraphQLNonNull, + GraphQLString, +} from 'graphql'; import { StrategyType } from '../types'; import { Strategy } from '../../models'; import { StrategyInputType } from '../types/input'; +import defaultStrategy from '../../templates/strategyTemplate'; export const args = { + fbSlug: { type: new GraphQLNonNull(GraphQLString) }, strategy: { type: StrategyInputType }, }; -const resolve = (parent, { strategy }) => { +const resolve = (parent, { fbSlug, strategy }) => { + if (strategy === undefined) { + strategy = defaultStrategy; + } + strategy.fbSlug = fbSlug; const newStrategy = new Strategy(strategy); return new Promise((res, rej) => { diff --git a/src/schema/mutations/EditStrategy.js b/src/schema/mutations/EditStrategy.js index a522b1441..8c864d537 100644 --- a/src/schema/mutations/EditStrategy.js +++ b/src/schema/mutations/EditStrategy.js @@ -8,16 +8,20 @@ import { StrategyInputType } from '../types/input'; const args = { id: { type: new GraphQLNonNull(GraphQLID) }, - newStrategy: { type: new GraphQLNonNull(StrategyInputType) }, + strategy: { type: new GraphQLNonNull(StrategyInputType) }, }; -const resolve = (parent, { id: _id, newStrategy }) => new Promise((res, rej) => { +const resolve = (parent, { id: _id, strategy: editedStrategy }) => new Promise((res, rej) => { Strategy.findOne({ _id }).exec((err, strategy) => { if (err) { rej(err); return; } - strategy = newStrategy; + strategy.history.push({ + updatedAt: strategy.updatedAt, + subStrategies: strategy.subStrategies, + }); + strategy.subStrategies = editedStrategy.subStrategies; strategy.save((error) => { if (error) { rej(error); @@ -29,7 +33,7 @@ const resolve = (parent, { id: _id, newStrategy }) => new Promise((res, rej) => }); const mutation = { - editParticipant: { + editStrategy: { type: StrategyType, args, resolve, diff --git a/src/schema/queries/StrategyByFB.js b/src/schema/queries/StrategyByFB.js index ba752f7c2..03b4005a1 100644 --- a/src/schema/queries/StrategyByFB.js +++ b/src/schema/queries/StrategyByFB.js @@ -1,23 +1,23 @@ import { GraphQLNonNull, - GraphQLID, + GraphQLString, } from 'graphql'; import { DatabaseError } from '../../errors'; import { StrategyType } from '../types'; import { Strategy } from '../../models'; const args = { - fbId: { type: new GraphQLNonNull(GraphQLID) }, + fbSlug: { type: new GraphQLNonNull(GraphQLString) }, }; -const resolve = (parent, { fbId }) => new Promise((res, rej) => { - Strategy.findOne({ fbId }).exec((err, strategy) => { +const resolve = (parent, { fbSlug }) => new Promise((res, rej) => { + Strategy.findOne({ fbSlug }).exec((err, strategy) => { if (err) { rej(err); return; } if (!strategy) { - rej(new DatabaseError('None of the strategy are linked to that fbId')); + rej(new DatabaseError('None of the strategy are linked to that fbSlug')); return; } res(strategy); diff --git a/src/schema/types/Strategy.js b/src/schema/types/Strategy.js index cb6fb1f69..034d8bcd7 100644 --- a/src/schema/types/Strategy.js +++ b/src/schema/types/Strategy.js @@ -2,6 +2,7 @@ import { GraphQLObjectType, GraphQLString, GraphQLList, + GraphQLBoolean, GraphQLID, } from 'graphql'; import { @@ -13,10 +14,14 @@ const Type = new GraphQLObjectType({ description: 'Everything you need to know about a strategy', fields: () => ({ id: { type: GraphQLID }, - fbId: { type: GraphQLString }, + fbSlug: { type: GraphQLString }, subStrategies: { + args: { activeOnly: { type: GraphQLBoolean } }, type: new GraphQLList(SubStrategyType), - resolve(parent) { + resolve(parent, { activeOnly }) { + if (activeOnly) { + return parent.subStrategies.filter(subStrategy => subStrategy.active); + } return parent.subStrategies; }, }, diff --git a/src/schema/types/SubStrategy.js b/src/schema/types/SubStrategy.js index 5d34e8caa..7facc4dc4 100644 --- a/src/schema/types/SubStrategy.js +++ b/src/schema/types/SubStrategy.js @@ -1,7 +1,7 @@ import { GraphQLObjectType, GraphQLString, - GraphQLList, + GraphQLBoolean, } from 'graphql'; import { EntryPointType, @@ -17,45 +17,46 @@ const Type = new GraphQLObjectType({ name: 'SubStrategy', description: 'SubStrategy composing a strategy', fields: () => ({ + active: { type: GraphQLBoolean }, name: { type: GraphQLString }, entryPoint: { - type: new GraphQLList(EntryPointType), + type: EntryPointType, resolve(parent) { return parent.entryPoint; }, }, exitPoint: { - type: new GraphQLList(ExitPointType), + type: ExitPointType, resolve(parent) { return parent.exitPoint; }, }, sellPoint: { - type: new GraphQLList(SellPointType), + type: SellPointType, resolve(parent) { return parent.sellPoint; }, }, buyPoint: { - type: new GraphQLList(BuyPointType), + type: BuyPointType, resolve(parent) { return parent.buyPoint; }, }, stopLoss: { - type: new GraphQLList(StopLossType), + type: StopLossType, resolve(parent) { return parent.stopLoss; }, }, buyOrder: { - type: new GraphQLList(BuyOrderType), + type: BuyOrderType, resolve(parent) { return parent.buyOrder; }, }, sellOrder: { - type: new GraphQLList(SellOrderType), + type: SellOrderType, resolve(parent) { return parent.sellOrder; }, diff --git a/src/schema/types/input/SubStrategy.js b/src/schema/types/input/SubStrategy.js index d55203ea8..08a56e37b 100644 --- a/src/schema/types/input/SubStrategy.js +++ b/src/schema/types/input/SubStrategy.js @@ -1,5 +1,6 @@ import { GraphQLString, + GraphQLBoolean, GraphQLInputObjectType, } from 'graphql'; import { @@ -16,6 +17,7 @@ const Type = new GraphQLInputObjectType({ name: 'SubStrategyInput', description: 'Payload for subStrategy input', fields: () => ({ + active: { type: GraphQLBoolean }, name: { type: GraphQLString }, entryPoint: { type: EntryPointInputType }, exitPoint: { type: ExitPointInputType }, diff --git a/src/schema/types/input/TheSevenMonolitics.js b/src/schema/types/input/TheSevenMonolitics.js index e7e32599d..8a6b3ba37 100644 --- a/src/schema/types/input/TheSevenMonolitics.js +++ b/src/schema/types/input/TheSevenMonolitics.js @@ -44,7 +44,7 @@ const StopLossInputType = new GraphQLInputObjectType({ name: 'StopLossTypeInput', description: 'Payload for stopLossType input', fields: () => ({ - situations: { type: new GraphQLList(PhaseInputType) }, + phases: { type: new GraphQLList(PhaseInputType) }, }), }); @@ -52,7 +52,7 @@ const BuyOrderInputType = new GraphQLInputObjectType({ name: 'BuyOrderTypeInput', description: 'Payload for buyOrderType input', fields: () => ({ - situations: { type: new GraphQLList(PhaseInputType) }, + phases: { type: new GraphQLList(PhaseInputType) }, }), }); @@ -60,7 +60,7 @@ const SellOrderInputType = new GraphQLInputObjectType({ name: 'SellOrderTypeInput', description: 'Payload for sellOrderType input', fields: () => ({ - situations: { type: new GraphQLList(PhaseInputType) }, + phases: { type: new GraphQLList(PhaseInputType) }, }), }); diff --git a/src/templates/strategyTemplate.js b/src/templates/strategyTemplate.js new file mode 100644 index 000000000..eb84a1325 --- /dev/null +++ b/src/templates/strategyTemplate.js @@ -0,0 +1,259 @@ +/* eslint-disable max-len */ +const strategy = { + subStrategies: [ + { + active: true, + name: 'Trend Following', + entryPoint: { + situations: [ + { + name: 'Price Collapsing', + conditions: [ + { + name: '%B Moving Average going up', + code: "percentageBandwidth.previous.direction === 'down' && percentageBandwidth.direction === 'down'", + }, + { + name: '%B Bandwidth going up', + code: 'percentageBandwidth.previous.previous.bandwidth < percentageBandwidth.previous.bandwidth && percentageBandwidth.previous.bandwidth < percentageBandwidth.bandwidth', + }, + { + name: 'Candles Min going down', + code: 'candle.previous.previous.min > candle.previous.min && candle.previous.min > candle.min', + }, + ], + }, + ], + }, + exitPoint: { + situations: [ + { + name: 'Market Reversing', + conditions: [ + { + name: 'Close above Band Moving Average', + code: 'candle.close > bollingerBand.movingAverage', + }, + ], + }, + ], + }, + sellPoint: { + situations: [ + { + name: 'Min below lower bollingerBand.', + conditions: [ + { + name: '3 Candles MIN below Lower Band', + code: 'candle.previous.previous.min < bollingerBand.previous.previous.movingAverage - bollingerBand.previous.previous.deviation && candle.previous.min < bollingerBand.previous.movingAverage - bollingerBand.previous.deviation && candle.min < bollingerBand.movingAverage - bollingerBand.deviation', + }, + ], + }, + ], + }, + stopLoss: { + phases: [ + { + name: 'Following Sell Rate', + code: 'newStopLoss = sellRate + sellRate * (stopLossPercentage - stopLossDecay) / 100', + situations: [ + { + name: 'Candle below Moving Average', + conditions: [ + { + name: 'Candle fully below Band Moving Average', + code: 'candle.max < bollingerBand.movingAverage', + }, + { + name: 'Band Moving Average going down', + code: "bollingerBand.direction === 'down'", + }, + ], + }, + ], + }, + { + name: 'Above Bands Moving Average', + code: 'newStopLoss = bollingerBand.movingAverage + bollingerBand.movingAverage * (stopLossPercentage - stopLossDecay) / 100', + situations: [ + { + name: 'Candle below Moving Average', + conditions: [ + { + name: 'Candle MAX below lower band', + code: 'candle.max < bollingerBand.movingAverage - bollingerBand.deviation', + }, + ], + }, + ], + }, + { + name: 'At Bands Moving Average', + code: 'newStopLoss = bollingerBand.movingAverage', + situations: [], + }, + ], + }, + buyOrder: { + phases: [ + { + name: '12 times standard deviation', + code: 'buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 12', + situations: [ + { + name: 'Candle cut by lower band', + conditions: [ + { + name: 'Max above lower band', + code: 'candle.max > bollingerBand.movingAverage - bollingerBand.deviation', + }, + { + name: 'MIN below lower band', + code: 'candle.min < bollingerBand.movingAverage - bollingerBand.deviation', + }, + { + name: 'Band Moving Average going down', + code: 'bollingerBand.previous.movingAverage > bollingerBand.movingAverage', + }, + ], + }, + ], + }, + { + name: '10 times standard deviation', + code: 'buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 10', + situations: [ + { + name: '%B starting to revert', + conditions: [ + { + name: '%B Moving Average going up', + code: "percentageBandwidth.direction === 'up'", + }, + { + name: '%B Moving Average above 0', + code: 'percentageBandwidth.movingAverage > 0', + }, + ], + }, + ], + }, + { + name: '4 times standard deviation', + code: 'buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 4', + situations: [ + { + name: '%B going down again', + conditions: [ + { + name: '%B Moving Average going down', + code: "percentageBandwidth.direction === 'down'", + }, + ], + }, + ], + }, + { + name: '3 times standard deviation', + code: 'buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 3', + situations: [ + { + name: '%B going up and above 30', + conditions: [ + { + name: '%B Moving Average going up', + code: "percentageBandwidth.direction === 'up'", + }, + { + name: '%B Moving Average above 30', + code: 'percentageBandwidth.movingAverage > 30', + }, + ], + }, + ], + }, + { + name: 'At lower band', + code: 'buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 2', + situations: [], + }, + ], + }, + }, + { + active: true, + name: 'Range Trading', + entryPoint: { + situations: [ + { + name: 'Gentle Up Trend', + conditions: [ + { + name: 'Sub-Channel going up', + code: "subChannel.direction === 'up'", + }, + { + name: 'Sub-Channel Side|Gentle|Medium', + code: "subChannel.slope === 'Side' || subChannel.slope === 'Gentle' || subChannel.slope === 'Medium'", + }, + { + name: 'Candle Close above Lower Band', + code: 'candle.close > bollingerBand.movingAverage + bollingerBand.deviation', + }, + ], + }, + ], + }, + exitPoint: { + situations: [ + { + name: 'Outside Sub-Channel', + conditions: [ + { + name: 'Going down or too Steep', + code: "subChannel.direction === 'Down' || subChannel.slope === 'Steep' || subChannel.slope === 'Extreme'", + }, + ], + }, + ], + }, + sellPoint: { + situations: [ + { + name: 'Min below lower bollingerBand.', + conditions: [ + { + name: '%B Moving Average going down', + code: 'percentageBandwidth.previous.movingAverage > percentageBandwidth.movingAverage', + }, + { + name: '%B Moving Average above 90', + code: 'percentageBandwidth.previous.movingAverage > 90', + }, + ], + }, + ], + }, + stopLoss: { + phases: [ + { + name: 'Following Sell Rate', + code: 'newStopLoss = sellRate + sellRate * (stopLossPercentage - stopLossDecay) / 100', + situations: [], + }, + ], + }, + buyOrder: { + phases: [ + { + name: 'Between Band Moving Average and Lower Band', + code: 'buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation', + situations: [], + }, + ], + }, + }, + ], +}; + +export default strategy;