Skip to content

Commit 1da340b

Browse files
EkliptorEkliptor
Ekliptor
authored and
Ekliptor
committed
added 3 pivot points indicators
1 parent 4aae6a2 commit 1da340b

14 files changed

+243
-8
lines changed

config/Back-Mixed.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
"tradeTotalBtc": 200.0,
77
"warmUpMin": 0,
88
"strategies": {
9-
"VolumeProfiler": {
9+
"PivotSniper": {
1010
"divergenceIndicator": "RSI",
11-
"interval": 48,
11+
"interval": 15,
1212
"volumeRows": 24,
1313
"valueAreaPercent": 70.0,
1414
"minVolumeSpike": 1.2,

config/DirectionFollower.json

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
},
7070
"StopLossTurn": {
7171
"order": "closeLong",
72+
"stop": 48.0,
7273
"setback": 5.0,
7374
"setbackLong": 5.5,
7475
"time": 1800,

gulpfile.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
var gulp = require('gulp');
2-
var typedoc = require("gulp-typedoc");
1+
const gulp = require('gulp');
2+
const typedoc = require("gulp-typedoc");
33

44
/*
55
gulp.task('default', function() {

src/Indicators/AbstractIndicator.ts

+9
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ export class VolumeProfileParams implements IntervalIndicatorParams {
9393
constructor() {
9494
}
9595
}
96+
export type PivotPointsType = "standard" | "fibonacci" | "demark";
97+
export class PivotPointsParams implements IntervalIndicatorParams {
98+
type: PivotPointsType = "standard";
99+
interval: number = 15; // the number of candles to use for high/low calculation
100+
enableLog: boolean;
101+
}
96102

97103
export type TrendDirection = Candle.TrendDirection; // moved, keep it as alias
98104

@@ -311,10 +317,13 @@ import "./MayerMultiple";
311317
import "./MFI";
312318
import "./NVTSignal";
313319
import "./OBV";
320+
import "./OrderbookHeatmap";
321+
import "./PivotPoints";
314322
import "./RSI";
315323
import "./SAR";
316324
import "./Sentiment";
317325
import "./SMA";
326+
import "./SocialSentiment";
318327
import "./STC";
319328
import "./Stochastic";
320329
import "./StochRSI";

src/Indicators/PivotPoints.ts

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import * as utils from "@ekliptor/apputils";
2+
const logger = utils.logger
3+
, nconf = utils.nconf;
4+
import {AbstractIndicator, MomentumIndicatorParams, PivotPointsParams} from "./AbstractIndicator";
5+
import {TaLib, TaLibParams, TaLibResult} from "./TaLib";
6+
import {Currency, Trade, Candle} from "@ekliptor/bit-models";
7+
8+
/**
9+
* An indicator calculating Standard Pivot Points.
10+
* // TODO Fibonacci Pivot Points and Demark Pivot Points
11+
* https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:pivot_points
12+
*/
13+
export default class PivotPoints extends AbstractIndicator {
14+
protected params: PivotPointsParams;
15+
protected candleHistory: Candle.Candle[] = [];
16+
protected pivotPoint: number = -1;
17+
protected resistance1: number = -1;
18+
protected resistance2: number = -1;
19+
protected resistance3: number = -1;
20+
protected support1: number = -1;
21+
protected support2: number = -1;
22+
protected support3: number = -1;
23+
protected high: number = -1;
24+
protected low: number = -1;
25+
protected close: number = -1;
26+
protected open: number = -1;
27+
28+
constructor(params: PivotPointsParams) {
29+
super(params)
30+
this.params = Object.assign(new PivotPointsParams(), this.params);
31+
}
32+
33+
public addCandle(candle: Candle.Candle) {
34+
return new Promise<void>((resolve, reject) => {
35+
this.addCandleHistory(candle);
36+
resolve();
37+
})
38+
}
39+
40+
public getValue() {
41+
return this.pivotPoint;
42+
}
43+
44+
public getPivotPoint() {
45+
return this.pivotPoint;
46+
}
47+
48+
public getResistance1() {
49+
return this.resistance1;
50+
}
51+
52+
public getResistance2() {
53+
return this.resistance2;
54+
}
55+
56+
public getResistance3() {
57+
return this.resistance3;
58+
}
59+
60+
public getSupport1() {
61+
return this.support1;
62+
}
63+
64+
public getSupport2() {
65+
return this.support2;
66+
}
67+
68+
public getSupport3() {
69+
return this.support3;
70+
}
71+
72+
public isReady() {
73+
return this.pivotPoint !== -1 && this.candleHistory.length >= this.params.interval;
74+
}
75+
76+
public getAllValues() {
77+
return {
78+
pivotPoint: this.pivotPoint,
79+
resistance1: this.resistance1,
80+
resistance2: this.resistance2,
81+
resistance3: this.resistance3,
82+
support1: this.support1,
83+
support2: this.support2,
84+
support3: this.support3
85+
}
86+
}
87+
88+
public serialize() {
89+
let state = super.serialize();
90+
state.candleHistory = this.candleHistory;
91+
state.pivotPoint = this.pivotPoint;
92+
state.resistance1 = this.resistance1;
93+
state.resistance2 = this.resistance2;
94+
state.resistance3 = this.resistance3;
95+
state.support1 = this.support1;
96+
state.support2 = this.support2;
97+
state.support3 = this.support3;
98+
state.high = this.high;
99+
state.low = this.low;
100+
state.close = this.close;
101+
state.open = this.open;
102+
return state;
103+
}
104+
105+
public unserialize(state: any) {
106+
super.unserialize(state);
107+
this.candleHistory = Candle.Candle.copy(state.candleHistory);
108+
this.pivotPoint = state.pivotPoint;
109+
this.resistance1 = state.resistance1;
110+
this.resistance2 = state.resistance2;
111+
this.resistance3 = state.resistance3;
112+
this.support1 = state.support1;
113+
this.support2 = state.support2;
114+
this.support3 = state.support3;
115+
this.high = state.high;
116+
this.low = state.low;
117+
this.close = state.close;
118+
this.open = state.open;
119+
}
120+
121+
// ################################################################
122+
// ###################### PRIVATE FUNCTIONS #######################
123+
124+
protected addCandleHistory(candle: Candle.Candle) {
125+
this.candleHistory.push(candle);
126+
if (this.candleHistory.length >= this.params.interval*2) {
127+
this.updatePivotPoints(); // calc from oldest candles (previous period)
128+
while (this.candleHistory.length > this.params.interval) // remove the 1st time period completely
129+
this.candleHistory.shift();
130+
console.log("cleaned", this.candleHistory.length)
131+
}
132+
else if (this.pivotPoint === -1 && this.candleHistory.length === this.params.interval)
133+
this.updatePivotPoints(); // initial update
134+
}
135+
136+
protected updatePivotPoints() {
137+
console.log("PIVOT update", this.candleHistory.length, this.candleHistory[this.candleHistory.length-1].start)
138+
this.calcHighLowClose();
139+
switch (this.params.type)
140+
{
141+
case "standard":
142+
this.pivotPoint = (this.high + this.low + this.close) / 3;
143+
this.support1 = this.pivotPoint*2 - this.high;
144+
this.support2 = this.pivotPoint - (this.high - this.low);
145+
this.resistance1 = this.pivotPoint*2 - this.low;
146+
this.resistance2 = this.pivotPoint + (this.high - this.low);
147+
break;
148+
case "fibonacci":
149+
this.pivotPoint = (this.high + this.low + this.close) / 3;
150+
this.support1 = this.pivotPoint - (0.382 * (this.high - this.low));
151+
this.support2 = this.pivotPoint - (0.618 * (this.high - this.low));
152+
this.support3 = this.pivotPoint - (1.0 * (this.high - this.low));
153+
this.resistance1 = this.pivotPoint + (0.382 * (this.high - this.low));
154+
this.resistance2 = this.pivotPoint + (0.618 * (this.high - this.low));
155+
this.resistance3 = this.pivotPoint + (1.0 * (this.high - this.low));
156+
break;
157+
case "demark":
158+
let x: number;
159+
if (this.close < this.open)
160+
x = this.high + 2*this.low + this.close;
161+
else if (this.close > this.open)
162+
x = 2*this.high + this.low + this.close;
163+
else // close == open
164+
x = this.high + this.low + 2*this.close;
165+
this.pivotPoint = x / 4;
166+
this.support1 = x/2 - this.high;
167+
this.resistance1 = x/2 - this.low;
168+
break;
169+
default:
170+
utils.test.assertUnreachableCode(this.params.type);
171+
}
172+
console.log(this.getAllValues())
173+
}
174+
175+
protected calcHighLowClose() {
176+
let high = 0, close = 0, open = -1, low = Number.MAX_VALUE;
177+
for (let i = 0; i < this.params.interval && i < this.candleHistory.length; i++)
178+
{
179+
// go through the first half of our candle history to get the HLC for this period
180+
const candle = this.candleHistory[i];
181+
if (open === -1)
182+
open = candle.open;
183+
if (candle.high > high)
184+
high = candle.high;
185+
if (candle.low < low)
186+
low = candle.low;
187+
close = candle.close;
188+
}
189+
this.high = high;
190+
this.low = low;
191+
this.close = close;
192+
this.open = open;
193+
}
194+
}
195+
196+
export {PivotPoints}

src/Indicators/SocialSentiment.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
// TODO add a generic indicator to be used by other (open source) devs that authenticates
3+
// with WolfBot sentiment data (paid & free)
4+
// https://wolfbot.org/social-sentiment/

src/Indicators/VolumeProfile.ts

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export default class VolumeProfile extends AbstractIndicator {
9292

9393
constructor(params: VolumeProfileParams) {
9494
super(params)
95+
this.params = Object.assign(new VolumeProfileParams(), this.params);
9596
}
9697

9798
public addCandle(candle: Candle.Candle) {

src/Lending/AbstractLendingTrader.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export abstract class AbstractLendingTrader extends AbstractGenericTrader {
158158
protected static coinStatusMap = new CoinStatusMap();
159159
protected static totalEquityMap = new TotalEquityMap();
160160
protected static submittedOffers = new OfferMap(); // to cancel offers that don't fill in time
161-
protected static updatePortfolioTimerID: number = -1;
161+
protected static updatePortfolioTimerID: NodeJS.Timer = null;
162162
protected static lastPortfolioUpdate: Date = new Date(0);
163163
//protected static lastMarginPositionUpdateMs: number = -1; // no need to sync portfolio with strategies
164164

src/Lending/LendingAdvisor.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ export class LendingAdvisor extends AbstractAdvisor {
523523
const configExchanges = this.pipeMarketStreams(config);
524524
connectedExchanges = connectedExchanges.concat(configExchanges);
525525
if (this.exchanges.has(configExchanges[0]) === false) {
526-
logger.error("Your config is set to use the exchange %s, but you don't have an API key set. Please add an API key and restart the app.", configExchanges[0]);
526+
logger.error("Your config is set to use the exchange %s, but you don't have an API key set. Please add an API key or change your config and restart the app.", configExchanges[0]);
527527
this.waitForRestart()
528528
return;
529529
}

src/Lending/Strategies/TechnicalLendingStrategy.ts

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {TechnicalAnalysis} from "../../Strategies/Mixins/TechnicalAnalysis";
1717
import {AbstractLendingStrategy, LendingStrategyAction} from "./AbstractLendingStrategy";
1818
import AverageVolume from "../../Indicators/AverageVolume";
1919
import VolumeProfile from "../../Indicators/VolumeProfile";
20+
import PivotPoints from "../../Indicators/PivotPoints";
2021

2122
export interface TechnicalLendingStrategyAction extends LendingStrategyAction {
2223
// TODO remove and only use TechnicalStrategAction? so far identical
@@ -133,6 +134,7 @@ export abstract class TechnicalLendingStrategy extends AbstractLendingStrategy i
133134
public getCryptotraderIndicator: (name: string) => AbstractCryptotraderIndicator;
134135
public getVolume: (name: string) => AverageVolume;
135136
public getVolumeProfile: (name: string) => VolumeProfile;
137+
public getPivotPoints: (name: string) => PivotPoints;
136138
public allIndicatorsReady: () => boolean;
137139
public getXMinuteMA: (minutes: number, period: number, depth: number) => Promise<number>;
138140
public computeCustomIndicator: (name: string, computeResult: Promise<number>) => Promise<number>;

src/Strategies/Mixins/TechnicalAnalysis.ts

+9
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {AbstractGenericStrategy} from "../AbstractGenericStrategy";
2020
import {TechnicalLendingStrategyAction} from "../../Lending/Strategies/TechnicalLendingStrategy";
2121
import AverageVolume from "../../Indicators/AverageVolume";
2222
import VolumeProfile from "../../Indicators/VolumeProfile";
23+
import PivotPoints from "../../Indicators/PivotPoints";
2324

2425

2526
export abstract class TechnicalAnalysis extends AbstractGenericStrategy {
@@ -174,6 +175,14 @@ export abstract class TechnicalAnalysis extends AbstractGenericStrategy {
174175
return null;
175176
}
176177

178+
public getPivotPoints(name: string): PivotPoints {
179+
let indicator = this.getIndicator(name);
180+
if (indicator instanceof PivotPoints)
181+
return indicator;
182+
logger.error("Indicator with name %s has the wrong instance type in %s", name, this.className)
183+
return null;
184+
}
185+
177186
public allIndicatorsReady() {
178187
let ready = true;
179188
for (let ind of this.indicators)

src/Strategies/TechnicalStrategy.ts

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {GenericStrategyState} from "./AbstractGenericStrategy";
2121
import {CandleBatcher} from "../Trade/Candles/CandleBatcher";
2222
import AverageVolume from "../Indicators/AverageVolume";
2323
import VolumeProfile from "../Indicators/VolumeProfile";
24+
import PivotPoints from "../Indicators/PivotPoints";
2425

2526
export interface TechnicalStrategyAction extends StrategyAction {
2627
// properties are optional because not all strategies will have all indicators (and some might inherit TechnicalStrategy for other functions)
@@ -190,6 +191,7 @@ export abstract class TechnicalStrategy extends AbstractStrategy implements Tech
190191
public getCryptotraderIndicator: (name: string) => AbstractCryptotraderIndicator;
191192
public getVolume: (name: string) => AverageVolume;
192193
public getVolumeProfile: (name: string) => VolumeProfile;
194+
public getPivotPoints: (name: string) => PivotPoints;
193195
public allIndicatorsReady: () => boolean;
194196
public getXMinuteMA: (minutes: number, period: number, depth: number) => Promise<number>;
195197
public computeCustomIndicator: (name: string, computeResult: Promise<number>) => Promise<number>;

src/TradeAdvisor.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ export default class TradeAdvisor extends AbstractAdvisor {
465465
const configExchanges = this.pipeMarketStreams(config);
466466
connectedExchanges = connectedExchanges.concat(configExchanges);
467467
if (this.exchanges.has(configExchanges[0]) === false) {
468-
logger.error("Your config is set to use the exchange %s, but you don't have an API key set. Please add an API key and restart the app.", configExchanges[0]);
468+
logger.error("Your config is set to use the exchange %s, but you don't have an API key set. Please add an API key or change your config and restart the app.", configExchanges[0]);
469469
this.waitForRestart()
470470
return;
471471
}

src/WebSocket/LoginUpdater.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,18 @@ export class LoginUpdater extends AppPublisher {
8282
}
8383
catch (err) {
8484
logger.error("Error checking new login data", err)
85-
return null;
85+
//return null;
86+
let res: LoginUpdateRes = {
87+
loginRes: {
88+
loginValid: false,
89+
subscriptionValid: false,
90+
apiKey: Object.keys(nconf.get("apiKeys"))[0]
91+
}
92+
}
93+
res.error = true;
94+
res.errorCode = "internalError";
95+
this.send(clientSocket, res) // usually called via http from main controller instead
96+
return res;
8697
}
8798
}
8899

0 commit comments

Comments
 (0)