1
\$\begingroup\$

This is a CLI utility for managing the allocation of investment. It's an exercise, don't take it too seriously. So the utility asks the user a series of questions about it's finances and suggests a balanced investment profile. This was supposed to be a marathon script, however due to a bug, marathon fails to update packages whenever one tries to use Vapor. It works first time, but not the second. But it is perfectly fine with Swift Package Manager.

I want help on controlling the unwieldy use of force unwraps, how do I avoid these? Also, general code style improvement would be greatly appreciated.

import CSVImporter // marathon: /home/luis/Dropbox/Documentos/Coding/Swift/Libraries/CSVImporter
import Console // marathon: https://github.com/vapor/console.git
import Files // marathon: https://github.com/JohnSundell/Files.git
import Docopt // marathon: https://github.com/lf-araujo/docopt.swift.git

extension Date {
    static func - (lhs: Date, rhs: Date) -> TimeInterval {
        return lhs.timeIntervalSinceReferenceDate - rhs.timeIntervalSinceReferenceDate
    }
}

let doc: String = """
Allocator

Passive allocation investment tool. It departs from three assumptions:
  1. the age when one wants to stop investing (should take life expectancy
    in your country into consideration);
  2. the user risk profile;
  3. that assets are grouped in three pools.
  3.1. the emergency fund, corresponds to expenses for six months (think six
    months of unemployment),
  3.2. the second pool corresponds to low risk investments
  3.3. the third pool are the high risk investments.

  Try to vary the types of investment within each pool, this tool will not handle
  investments within pool, only the total pool value. Decision on what is low
  and high risk, as well as decision on the risk profile is entirely on users
  discretion. Every year, the tool will help the user allocate the correct
  amount in each of the pools, based in a simple algorithm. Finally, since
  sometimes one needs to quickly record a deposit into one of the investments
  , there is a quick-add add command, in which one can add the latest
  low risk and high risk investment without going trough all the questions.

Usage:
 allocator [(quick-add <low> <high>)]

Options:
 -h --help         Shows this screen
 --version

"""
// Managing arguments
var args = CommandLine.arguments
args.remove(at: 0)
let argument = Docopt.parse(doc, argv: args, help: true, version: "0.0.1")

//  Setting today's date
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let today = formatter.string(from: date)

// Setting the terminal interface
let terminal = Terminal()

let importer: [String:String]
let importedRecords: [[String:String]]

var header: String = ""
var content: String = ""

// If this is not the first run
if Folder.current.files.names.contains("finances.csv") {
    let importer = CSVImporter<[String: String]>(path: "finances.csv")
    let importedRecords = importer.importRecords(structure: { (headerValues) -> Void in
        header = headerValues.compactMap {  $0 }.joined(separator: ",") + "\n"
    }) { $0 }

    if argument["quick-add"] as? String == "true" {
        var count: Int = 0
        for record in importedRecords {
            content += "\(record["date"]!),\(record["investpercent"]!),\(record["expenses"]!),\(record["savings"]!),"
            content += "\(record["low"]!),\(record["high"]!),\(record["objective"]!)\n" //second line
            count += 1
            if count == importedRecords.endIndex {
                content += "\(today),\(record["investpercent"]!),\(record["expenses"]!),\(record["savings"]!),"
                content += "\(argument["<low>"]!),\(argument["<high>"]!),\(record["objective"]!)\n"
            }
        }
        try Folder.current.file(named: "finances.csv").append(string: content)

    } else {
        let reversedRecords = importedRecords.reversed()
        let zipped = zip(reversedRecords.dropFirst(), reversedRecords)
        var lastPercentUpdate: String = ""

        if let twoDictionaries = zipped.first(where: {$0["investpercent"] != $1["investpercent"]}),
        let date = twoDictionaries.1["date"] {
            lastPercentUpdate = date
        }

        let lastentry = Array(importedRecords.suffix(1))
        var investpercent: Double = Double(lastentry[0]["investpercent"]!)!
        var expenses: Double = Double(lastentry[0]["expenses"]!)!

        if (formatter.date(from: today)!  - formatter.date(from: lastPercentUpdate)!) > 15778800 {
            //print(lastentry[0]["date"])           
            expenses = Double(terminal.ask("""
                It's been more than 6 months since you updated the amount you should be saving
                on the emergency pool. Please inform how much are your cummulative expenses in
                6 months:
                """))!
            investpercent = Double(lastentry[0]["investpercent"]!)! - 1.0

        }

        let investedlow = Double(terminal.ask("Invested value in low risk since last time:")) ?? 0.0
        let investedhigh = Double(terminal.ask("Invested value in high risk since last time:")) ?? 0.0
        let lowtoday = Double(terminal.ask("Total low risk value today:")) ?? Double(lastentry[0]["low"]!)!
        let hightoday = Double(terminal.ask("Total high risk value today:")) ?? Double(lastentry[0]["high"]!)!
        let savings = Double(terminal.ask("Enter your current savings account status:")) ?? Double(lastentry[0]["savings"]!)!
        let invest = Double(terminal.ask("Finally, how much you have to save today:")) ?? 0.0

        let aimhigh = ((lowtoday + hightoday) * investpercent) / 100
        let aimlow = ((lowtoday + hightoday) * (100 - investpercent)) / 100

        if expenses > savings {
            terminal.print("""
                It's time to update the savings account, deposit \(expenses - savings).
                """)

        } else {
            if investpercent <= 0 {
                investpercent = 0
                print("""
                    Congratulations! You've reached the year which you wanted to stop moving money
                    around! Don't forget to consider converting all high risk investiment into types
                    of high risk investiment that generates dividends.
                    """)
            }
        }
    let investhigh =  (aimhigh / (aimhigh + aimlow)) * invest
    let investlow = (aimlow / (aimhigh + aimlow)) * invest
    let objective = expenses - ( (lowtoday + hightoday) - (investedlow + investedhigh) - (Double(lastentry[0]["low"]!)! + Double(lastentry[0]["high"]!)!))

    print("\nDeposit \(investhigh) into the high risk pool.\n")
    print("Deposit \(investlow) into the low risk pool.\n")

    // salvar o arquivo
    content += "\(today),\(investpercent),\(expenses),\(savings),\(lowtoday),\(hightoday),\(objective)\n"
    try Folder.current.file(named: "finances.csv").append(string: content)
    print("Your data has been saved to finances.csv in this directory. Goodbye!\n")

    }

} else if !Folder.current.files.names.contains("finances.csv") { // This is the first run
    let age = Double(terminal.ask("What is your age?")) ?? 0.0
    let end = Double(terminal.ask("In which age you want to stop managing finances (default: 80):")) ?? 80.0
    let profile = Double(terminal.ask("""
        What is your risk profile: low risk (type 40, this is the default),
        medium risk (type 20), high risk (type 0).
        Intermediary values are accepted:
        """)) ?? 40
    let expenses = Double(terminal.ask("Enter your current cummulative 6 months expenses:")) ?? 0.0
    let savings = Double(terminal.ask("Enter your current savings account volume:")) ?? 0.0
    let low = Double(terminal.ask("Enter your low risk investiments total:")) ?? 0.0
    let high = Double(terminal.ask("Enter your high risk investiments total:")) ?? 0.0
    let invest = Double(terminal.ask("Finally, how much you have to save or invest today (default: 0):")) ?? 0.0

    if expenses > savings {
        terminal.print("""
            You haven't reached the first step of creating the emergency pool. Deposit \(invest) now
            into your savings acount and keep doing it suntil it reaches \(expenses)\n Good luck!
            """)
    } else {
        let highobjective = ((end - profile - age) / 100) * invest
        let lowobjective = invest - highobjective

        terminal.print("""
            Deposit \(lowobjective) into your low risk investiment pool.
            Deposit \(highobjective) into your high risk investiment pool.
            Your data has been saved to finances.csv in this directory. Goodbye!
            """)
        var content: String = "date,investpercent,expenses,savings,low,high,objective\n"
        content += "\(today),\(end - profile - age),\(expenses),\(savings),\(low),\(high),\(expenses / 6)\n"

        let outfile = try Folder.current.createFile(named: "finances.csv")
        try outfile.write(string: header + content)
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.