Relative Strength Index in Swift - swift

I am trying to code an RSI (which has been a good way for me to learn API data fetching and algorithms already).
The API I am fetching data from comes from a reputable exchange so I know the values my algorithm is analyzing are correct, that's a good start.
The issue I'm having is that the result of my calculations are completely off from what I can read on that particular exchange and which also provides an RSI indicator (I assume they analyze their own data, so the same data as I have).
I used the exact same API to translate the Ichimoku indicator into code and this time everything is correct! I believe my RSI calculations might be wrong somehow but I've checked and re-checked many times.
I also have a "literal" version of the code where every step is calculated like an excel sheet. It's pretty stupid in code but it validates the logic of the calculation and the results are the same as the following code.
Here is my code to calculate the RSI :
let period = 14
// Upward Movements and Downward Movements
var upwardMovements : [Double] = []
var downwardMovements : [Double] = []
for idx in 0..<15 {
let diff = items[idx + 1].close - items[idx].close
upwardMovements.append(max(diff, 0))
downwardMovements.append(max(-diff, 0))
}
// Average Upward Movements and Average Downward Movements
let averageUpwardMovement1 = upwardMovements[0..<period].reduce(0, +) / Double(period)
let averageDownwardMovement1 = downwardMovements[0..<period].reduce(0, +) / Double(period)
let averageUpwardMovement2 = (averageUpwardMovement1 * Double(period - 1) + upwardMovements[period]) / Double(period)
let averageDownwardMovement2 = (averageDownwardMovement1 * Double(period - 1) + downwardMovements[period]) / Double(period)
// Relative Strength
let relativeStrength1 = averageUpwardMovement1 / averageDownwardMovement1
let relativeStrength2 = averageUpwardMovement2 / averageDownwardMovement2
// Relative Strength Index
let rSI1 = 100 - (100 / (relativeStrength1 + 1))
let rSI2 = 100 - (100 / (relativeStrength2 + 1))
// Relative Strength Index Average
let relativeStrengthAverage = (rSI1 + rSI2) / 2
BitcoinRelativeStrengthIndex.bitcoinRSI = relativeStrengthAverage
Readings at 3:23pm this afternoon give 73.93 for my algorithm and 18.74 on the exchange. As the markets are crashing right now and I have access to different RSIs on different exchanges, they all display an RSI below 20 so my calculations are off.
Do you guys have any idea?

I am answering this 2 years later, but hopefully it helps someone.
RSI gets more precise the more data points you feed into it. For a default RSI period of 14, you should have at least 200 previous data points. The more, the better!
Let's suppose you have an array of close candle prices for a given market. The following function will return RSI values for each candle. You should always ignore the first data points, since they are not precise enough or the number of candles is not the 14 (or whatever your periods number is).
func computeRSI(on prices: [Double], periods: Int = 14, minimumPoints: Int = 200) -> [Double] {
precondition(periods > 1 && minimumPoints > periods && prices.count >= minimumPoints)
return Array(unsafeUninitializedCapacity: prices.count) { (buffer, count) in
buffer.initialize(repeating: 50)
var (previousPrice, gain, loss) = (prices[0], 0.0, 0.0)
for p in stride(from: 1, through: periods, by: 1) {
let price = prices[p]
let value = price - previousPrice
if value > 0 {
gain += value
} else {
loss -= value
}
previousPrice = price
}
let (numPeriods, numPeriodsMinusOne) = (Double(periods), Double(periods &- 1))
var avg = (gain: gain / numPeriods, loss: loss /numPeriods)
buffer[periods] = (avg.loss > .zero) ? 100 - 100 / (1 + avg.gain/avg.loss) : 100
for p in stride(from: periods &+ 1, to: prices.count, by: 1) {
let price = prices[p]
avg.gain *= numPeriodsMinusOne
avg.loss *= numPeriodsMinusOne
let value = price - previousPrice
if value > 0 {
avg.gain += value
} else {
avg.loss -= value
}
avg.gain /= numPeriods
avg.loss /= numPeriods
if avgLoss > .zero {
buffer[p] = 100 - 100 / (1 + avg.gain/avg.loss)
} else {
buffer[p] = 100
}
previousPrice = price
}
count = prices.count
}
}
Please note that the code is very imperative to reduce the amount of operations/loops and get the maximum compiler optimizations. You might be able to squeeze more performance using the Accelerate framework, though. We are also handling the edge case where you might get all gains or losses in a periods range.
If you want to have a running RSI calculation. Just store the last RSI value and perform the RSI equation for the new price.

Related

Swift unreliable round() method to 2 decimal places

I'm trying to calculate the time between two doubles (distance, speed) to 2 decimal places using swift round() method but there are instances where its unreliable and I end up with something like 58.000000001. I tried to hack fix this by rounding again but even that doesn't work on larger numbers eg 734.00000001 Is there a way to fix this or another way to calculate time between two doubles?
var totalTime: Double = 0
for 0...100 {
let calcTime = 35.3 / 70
let roundedTime = round(calcTime * 100) / 100.0
print("TIME === \(roundedTime)")
totalTime += round(totalTime * 100) / 100 + roundedTime // round again to clamp the extra zero's
print("Total time \(totalTime)")
}

ios charts - Looking for a way to make custom intervals on the y-axis - swift

I'm looking for a way to set custom intervals on the y-axis in ios charts. So, instead of the precalculated interval of 100, I'm looking for a way to change the step to 60.
Instead of this:
I'm looking for this:
I solved my own problem by using a YAxisRenderer. I set up a custom YAxisRenderer and changed the computeAxisValues function to suit my own needs. Here is my code:
import Charts
class TimelineYAxisRender: YAxisRenderer{
/// Sets up the axis values. Computes the desired number of labels between the two given extremes.
#objc open override func computeAxisValues(min: Double, max: Double)
{
super.computeAxisValues(min: min, max: max)
guard let axis = self.axis else { return }
let labelCount = axis.labelCount
let range = max - min
// Ensure stops contains at least n elements.
axis.entries.removeAll(keepingCapacity: true)
axis.entries.reserveCapacity(labelCount)
let lowestHour = Int((min/60).rounded(.down))
let highestHour = Int((max/60).rounded(.up))
let rangeHour = highestHour - lowestHour
switch range {
case 160..<600:
for i in 0...2 * rangeHour{
axis.entries.append(Double(i) * 30 + Double(lowestHour * 60))
}
case 50..<160:
for i in 0...4 * rangeHour{
axis.entries.append(Double(i) * 15 + Double(lowestHour * 60))
}
case 15..<50:
for i in 0...12 * rangeHour{
axis.entries.append(Double(i) * 5 + Double(lowestHour * 60))
}
case 0..<15:
for i in 0...60 * rangeHour{
axis.entries.append(Double(i) + Double(lowestHour * 60))
}
default:
for i in 0...8{
axis.entries.append(Double(i) * 180)
}
}
}
}
My code to set the left y-axis to the new YAxisRenderer:
let transformer = barChart.getTransformer(forAxis:.left)
let viewPortHandler = barChart.leftYAxisRenderer.viewPortHandler
barChart.leftYAxisRenderer = TimelineYAxisRender(viewPortHandler: viewPortHandler, yAxis: barChart.leftAxis, transformer: transformer)
I used a rounding system so I would only load the labels for the part of the graph the user can see, as loading every label would take up too much memory and cause the simulator to stutter. I also used the max - min to determine what labels to load to create a smooth zoom. When the new labels are loaded can be changed by changing the parameters for the switch cases, and the amount of zoom can be changed by changing what is actually appended to axis.entries.

Best way to find time segments in a time frame

I am currently making an app where I have to retrieve allowances for certain hours worked.
For example, I work from 3:00 pm to 10:00 pm.
Between this time an allowance would be given between 8:00 pm - 10:00 pm 20% and from 10:00 pm to 11:00 pm 35%.
Until now I did not get much further than a rather complicated if construction.
if startUur <= 20 && eindUur >= 22 || 20..<22 ~= startUur || 20..<22 ~= eindUur || startUur > 20 && eindUur <= 22 && startMinuut > 0 || startUur < 20 {
//code to calculate allowance
}
I have been looking for a good and better way to do this, but I cannot find it.
Is there a better way or am I bound to such a way with an if construction?
struct TimeAndMoneyFrame {
var timeHourStart:Int = 0
var timeHourEnd:Int = 0
var percentage: Double = 0
}
extension TimeAndMoneyFrame {
func timeLength() -> Int {
return timeHourEnd - timeHourStart
}
}
let 2000to2200 = TimeAndMoneyFrame(timeHourStart = 20, timeHourEnd = 22, percentage = 0.2)
let 2200to2300 = TimeAndMoneyFrame(timeHourStart = 22, timeHourEnd = 23, percentage = 0.35)
let timeArray:[TimeAndMoneyFrame] = [2000to2200]
//Assuming 'startUur' means startingHour
//Assuming 'eindUur' means endingHour
let startUur:Int = someHour // You define some hour
let eindUure:Int = someHour // You define some hour
let hourlyRateOfPay: Double = somePay // You define some pay
var totalPay: Double = 0
for time in timeArray {
let start = time.timeHourStart
let end = time.timeHourEnd
let percent = time.percentage
//Encompasses entirely
if(start > startUur && end < eindUur) {
totalPay += (hourlyRateOfPay * (1 + percent) * time.timeLength())
}
//Only encompassed the left side - i.e., their time worked ends within this time frame
else if(start > startUur) {
let timeWithinThisFrame = eindUur - start
totalPay += (hourlyRateOfPay * (1 + percent) * (timeWithinThisFrame/time.timeLength())
}
//Only encompassed right side - i.e., the beginning of the starts within this time frame
else if(eindUur < end) {
let timeWithinThisFrame = end - startUur
totalPay += (hourlyRateOfPay * (1 + percent) * (timeWithinThisFrame/time.timeLength())
}
}
So, we can break this problemn up into a couple problems.
1) Defining a struct/class that encompasses a time frame with an associated percentage
2) The algorithm necessary to calculate the total pay
I have a base assumption.
1) You say allowances - a person gets 35% if they work from 2200-2300 - I took this as a bonus so say 1.35% of the normal hourly rate. If this is not the case, the idea of consuming time frames one at a time would be the same. The only difference might be the totalPay calculation.
My algorithm goes through each time frame and determines if the current time intercepts any of the time frames. If it does, I calculate how much it intercepts and calculate the rate of pay.
Note: I coded all of this on SO - there might be some syntax issues.

Why can't I divide integers correctly within reduce in Swift?

I'm trying to get the average of an array of Ints using the following code:
let numbers = [1,2,3,4,5]
let avg = numbers.reduce(0) { return $0 + $1 / numbers.count }
print(avg) // 1
Which is obviously incorrect. However, if I remove the division to the outside of the closure:
let numbers = [1,2,3,4,5]
let avg = numbers.reduce(0) { return $0 + $1 } / numbers.count
print(avg) // 3
Bingo! I think I remember reading somewhere (can't recall if it was in relation to Swift, JavaScript or programming math in general) that this has something to do with the fact that dividing the sum by the length yields a float / double e.g. (1 + 2) / 5 = 0.6 which will be rounded down within the sum to 0. However I would expect ((1 + 2) + 3) / 5 = 1.2 to return 1, however it too seems to return 0.
With doubles, the calculation works as expected whichever way it's calculated, as long as I box the count integer to a double:
let numbers = [1.0,2.0,3.0,4.0,5.0]
let avg = numbers.reduce(0) { return $0 + $1 / Double(numbers.count) }
print(avg) // 3
I think I understand the why (maybe not?). But I can't come up with a solid example to prove it.
Any help and / or explanation is very much appreciated. Thanks.
The division does not yield a double; you're doing integer division.
You're not getting ((1 + 2) + 3 etc.) / 5.
In the first case, you're getting (((((0 + (1/5 = 0)) + (2/5 = 0)) + (3/5 = 0)) + (4/5 = 0)) + (5/5 = 1)) = 0 + 0 + 0 + 0 + 0 + 1 = 1.
In the second case, you're getting ((((((0 + 1) + 2) + 3) + 4) + 5) / 5) = 15 / 5 = 3.
In the third case, double precision loss is much smaller than the integer, and you get something like (((((0 + (1/5.0 = 0.2)) + (2/5.0 = 0.4)) + (3/5.0 = 0.6)) + (4/5.0 = 0.8)) + (5/5.0 = 1.0)).
The problem is that what you are attempting with the first piece of code does not make sense mathematically.
The average of a sequence is the sum of the entire sequence divided by the number of elements.
reduce calls the lambda function for every member of the collection it is being called on. Thus you are summing and dividing all the way through.
For people finding it hard to understand the original answer.
Consider.
let x = 4
let y = 3
let answer = x/y
You expect the answer to be a Double, but no, it is an Int. For you to get an answer which is not a rounded down Int. You must explicitly state the values to be Double. See below
let doubleAnswer = Double(x)/Double(y)
Hope this helped.

How to get an SKAction.sequence to wait for a random duration

I'm trying to get a door to open up, wait for a random amount of time (within a range) and then close. If I use SKAction.waitForDuration, I can set the exact time to wait, and that works. However, if I use SKAction.waitForDuration (withRange), it always opens exactly at the shortest time in the range. How to I get it to open at other times within the range? Any help would be greatly appreciated! Thanks!
Here's my code:
var doorAction = SKAction.moveTo(CGPoint(x: size.width * 0.5, y: size.height + size.height * 1.95), duration: NSTimeInterval(1))
// randomWait should give me a value between 10 & 30, but door always opens at 10
var randomWait = SKAction.waitForDuration(20.0, withRange: 20.0)
// waitAction works fine, but I want a value between 10 and 30
// var waitAction = SKAction.waitForDuration(10)
var doorReturnAction = SKAction.moveTo(CGPoint(x: size.width * 0.5, y: size.height * 2.18), duration: NSTimeInterval(1))
var actionSequence = SKAction.sequence([doorAction, randomWait, doorReturnAction])
self.runAction(actionSequence)
Since you are not repeating the action sequence you don't really need SKAction.waitForDuration:withRange:. You can calculate a waitDuration using arc4random()
let upperlimit : UInt32 = 30
let lowerlimit : UInt32 = 10
let waitDuration = NSTimeInterval(CGFloat(arc4random() % ((upperlimit - lowerlimit) * 10) + lowerlimit * 10)/10.0)
var randomWait = SKAction.waitForDuration(waitDuration)
From the docs.
sec - The average amount of time to wait.
durationRange - The range of possible values for the duration.
If you set 20 to be the average, and the range only goes up to 20 then the only way it can meet that is to always open at 20. You should try having the first parameter (the average) be somewhere close to the middle of the range, if you want the behavior to be less uniform.