commit d7ae2436d2329b1efdd686a4329281a23eaae580 Author: ened Date: Tue May 16 23:35:46 2023 +0900 Add KissMe framework and console command line tool diff --git a/KissMe.xcodeproj/project.pbxproj b/KissMe.xcodeproj/project.pbxproj new file mode 100644 index 0000000..c2bec7c --- /dev/null +++ b/KissMe.xcodeproj/project.pbxproj @@ -0,0 +1,582 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 341F5EB02A0A80EC00962D48 /* KissMe.docc in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EAF2A0A80EC00962D48 /* KissMe.docc */; }; + 341F5EB62A0A80EC00962D48 /* KissMe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 341F5EAB2A0A80EC00962D48 /* KissMe.framework */; }; + 341F5EBB2A0A80EC00962D48 /* KissMeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EBA2A0A80EC00962D48 /* KissMeTests.swift */; }; + 341F5EBC2A0A80EC00962D48 /* KissMe.h in Headers */ = {isa = PBXBuildFile; fileRef = 341F5EAE2A0A80EC00962D48 /* KissMe.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 341F5EDE2A0F300100962D48 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EDD2A0F300100962D48 /* Request.swift */; }; + 341F5EE12A0F373B00962D48 /* Login.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EE02A0F373B00962D48 /* Login.swift */; }; + 341F5EE52A0F3EF400962D48 /* DomesticStock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EE42A0F3EF400962D48 /* DomesticStock.swift */; }; + 341F5EE92A0F87FB00962D48 /* DomesticStockPrice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EE82A0F87FB00962D48 /* DomesticStockPrice.swift */; }; + 341F5EEC2A0F883900962D48 /* ForeignStock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EEB2A0F883900962D48 /* ForeignStock.swift */; }; + 341F5EEE2A0F884300962D48 /* ForeignStockPrice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EED2A0F884300962D48 /* ForeignStockPrice.swift */; }; + 341F5EF02A0F886600962D48 /* ForeignFutures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EEF2A0F886600962D48 /* ForeignFutures.swift */; }; + 341F5EF22A0F887200962D48 /* DomesticFutures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EF12A0F887200962D48 /* DomesticFutures.swift */; }; + 341F5EF52A0F891200962D48 /* KissAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EF42A0F891200962D48 /* KissAccount.swift */; }; + 341F5EF72A0F8B0500962D48 /* DomesticStockResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EF62A0F8B0500962D48 /* DomesticStockResult.swift */; }; + 341F5EF92A0F907300962D48 /* DomesticStockPriceResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EF82A0F907300962D48 /* DomesticStockPriceResult.swift */; }; + 341F5EFB2A10909D00962D48 /* LoginResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EFA2A10909D00962D48 /* LoginResult.swift */; }; + 341F5EFD2A10931B00962D48 /* DomesticStockSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EFC2A10931B00962D48 /* DomesticStockSearch.swift */; }; + 341F5EFF2A10955D00962D48 /* OrderRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5EFE2A10955D00962D48 /* OrderRequest.swift */; }; + 341F5F012A11155100962D48 /* DomesticStockSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F002A11155100962D48 /* DomesticStockSearchResult.swift */; }; + 341F5F032A11A2BC00962D48 /* Credential.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F022A11A2BC00962D48 /* Credential.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 341F5EB72A0A80EC00962D48 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 341F5EA22A0A80EC00962D48 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 341F5EAA2A0A80EC00962D48; + remoteInfo = KissMe; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 341F5EAB2A0A80EC00962D48 /* KissMe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KissMe.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 341F5EAE2A0A80EC00962D48 /* KissMe.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KissMe.h; sourceTree = ""; }; + 341F5EAF2A0A80EC00962D48 /* KissMe.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = KissMe.docc; sourceTree = ""; }; + 341F5EB52A0A80EC00962D48 /* KissMeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KissMeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 341F5EBA2A0A80EC00962D48 /* KissMeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissMeTests.swift; sourceTree = ""; }; + 341F5EDD2A0F300100962D48 /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; }; + 341F5EE02A0F373B00962D48 /* Login.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Login.swift; sourceTree = ""; }; + 341F5EE42A0F3EF400962D48 /* DomesticStock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticStock.swift; sourceTree = ""; }; + 341F5EE82A0F87FB00962D48 /* DomesticStockPrice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticStockPrice.swift; sourceTree = ""; }; + 341F5EEB2A0F883900962D48 /* ForeignStock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForeignStock.swift; sourceTree = ""; }; + 341F5EED2A0F884300962D48 /* ForeignStockPrice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForeignStockPrice.swift; sourceTree = ""; }; + 341F5EEF2A0F886600962D48 /* ForeignFutures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForeignFutures.swift; sourceTree = ""; }; + 341F5EF12A0F887200962D48 /* DomesticFutures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticFutures.swift; sourceTree = ""; }; + 341F5EF42A0F891200962D48 /* KissAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KissAccount.swift; sourceTree = ""; }; + 341F5EF62A0F8B0500962D48 /* DomesticStockResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticStockResult.swift; sourceTree = ""; }; + 341F5EF82A0F907300962D48 /* DomesticStockPriceResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticStockPriceResult.swift; sourceTree = ""; }; + 341F5EFA2A10909D00962D48 /* LoginResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginResult.swift; sourceTree = ""; }; + 341F5EFC2A10931B00962D48 /* DomesticStockSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticStockSearch.swift; sourceTree = ""; }; + 341F5EFE2A10955D00962D48 /* OrderRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderRequest.swift; sourceTree = ""; }; + 341F5F002A11155100962D48 /* DomesticStockSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomesticStockSearchResult.swift; sourceTree = ""; }; + 341F5F022A11A2BC00962D48 /* Credential.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credential.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 341F5EA82A0A80EC00962D48 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 341F5EB22A0A80EC00962D48 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 341F5EB62A0A80EC00962D48 /* KissMe.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 341F5EA12A0A80EC00962D48 = { + isa = PBXGroup; + children = ( + 341F5EAD2A0A80EC00962D48 /* KissMe */, + 341F5EB92A0A80EC00962D48 /* KissMeTests */, + 341F5EAC2A0A80EC00962D48 /* Products */, + ); + sourceTree = ""; + }; + 341F5EAC2A0A80EC00962D48 /* Products */ = { + isa = PBXGroup; + children = ( + 341F5EAB2A0A80EC00962D48 /* KissMe.framework */, + 341F5EB52A0A80EC00962D48 /* KissMeTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 341F5EAD2A0A80EC00962D48 /* KissMe */ = { + isa = PBXGroup; + children = ( + 341F5EF32A0F88AC00962D48 /* Common */, + 341F5EEA2A0F882300962D48 /* Foreign */, + 341F5EE62A0F3EFB00962D48 /* Domestic */, + 341F5EDF2A0F372000962D48 /* Login */, + 341F5EAE2A0A80EC00962D48 /* KissMe.h */, + 341F5EAF2A0A80EC00962D48 /* KissMe.docc */, + 341F5EF42A0F891200962D48 /* KissAccount.swift */, + ); + path = KissMe; + sourceTree = ""; + }; + 341F5EB92A0A80EC00962D48 /* KissMeTests */ = { + isa = PBXGroup; + children = ( + 341F5EBA2A0A80EC00962D48 /* KissMeTests.swift */, + ); + path = KissMeTests; + sourceTree = ""; + }; + 341F5EDF2A0F372000962D48 /* Login */ = { + isa = PBXGroup; + children = ( + 341F5EE02A0F373B00962D48 /* Login.swift */, + 341F5EFA2A10909D00962D48 /* LoginResult.swift */, + ); + path = Login; + sourceTree = ""; + }; + 341F5EE62A0F3EFB00962D48 /* Domestic */ = { + isa = PBXGroup; + children = ( + 341F5EE42A0F3EF400962D48 /* DomesticStock.swift */, + 341F5EF62A0F8B0500962D48 /* DomesticStockResult.swift */, + 341F5EE82A0F87FB00962D48 /* DomesticStockPrice.swift */, + 341F5EF82A0F907300962D48 /* DomesticStockPriceResult.swift */, + 341F5EFC2A10931B00962D48 /* DomesticStockSearch.swift */, + 341F5F002A11155100962D48 /* DomesticStockSearchResult.swift */, + 341F5EF12A0F887200962D48 /* DomesticFutures.swift */, + ); + path = Domestic; + sourceTree = ""; + }; + 341F5EEA2A0F882300962D48 /* Foreign */ = { + isa = PBXGroup; + children = ( + 341F5EEB2A0F883900962D48 /* ForeignStock.swift */, + 341F5EED2A0F884300962D48 /* ForeignStockPrice.swift */, + 341F5EEF2A0F886600962D48 /* ForeignFutures.swift */, + ); + path = Foreign; + sourceTree = ""; + }; + 341F5EF32A0F88AC00962D48 /* Common */ = { + isa = PBXGroup; + children = ( + 341F5EDD2A0F300100962D48 /* Request.swift */, + 341F5F022A11A2BC00962D48 /* Credential.swift */, + 341F5EFE2A10955D00962D48 /* OrderRequest.swift */, + ); + path = Common; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 341F5EA62A0A80EC00962D48 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 341F5EBC2A0A80EC00962D48 /* KissMe.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 341F5EAA2A0A80EC00962D48 /* KissMe */ = { + isa = PBXNativeTarget; + buildConfigurationList = 341F5EBF2A0A80EC00962D48 /* Build configuration list for PBXNativeTarget "KissMe" */; + buildPhases = ( + 341F5EA62A0A80EC00962D48 /* Headers */, + 341F5EA72A0A80EC00962D48 /* Sources */, + 341F5EA82A0A80EC00962D48 /* Frameworks */, + 341F5EA92A0A80EC00962D48 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = KissMe; + productName = KissMe; + productReference = 341F5EAB2A0A80EC00962D48 /* KissMe.framework */; + productType = "com.apple.product-type.framework"; + }; + 341F5EB42A0A80EC00962D48 /* KissMeTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 341F5EC22A0A80EC00962D48 /* Build configuration list for PBXNativeTarget "KissMeTests" */; + buildPhases = ( + 341F5EB12A0A80EC00962D48 /* Sources */, + 341F5EB22A0A80EC00962D48 /* Frameworks */, + 341F5EB32A0A80EC00962D48 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 341F5EB82A0A80EC00962D48 /* PBXTargetDependency */, + ); + name = KissMeTests; + productName = KissMeTests; + productReference = 341F5EB52A0A80EC00962D48 /* KissMeTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 341F5EA22A0A80EC00962D48 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1430; + LastUpgradeCheck = 1430; + TargetAttributes = { + 341F5EAA2A0A80EC00962D48 = { + CreatedOnToolsVersion = 14.3; + }; + 341F5EB42A0A80EC00962D48 = { + CreatedOnToolsVersion = 14.3; + }; + }; + }; + buildConfigurationList = 341F5EA52A0A80EC00962D48 /* Build configuration list for PBXProject "KissMe" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 341F5EA12A0A80EC00962D48; + productRefGroup = 341F5EAC2A0A80EC00962D48 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 341F5EAA2A0A80EC00962D48 /* KissMe */, + 341F5EB42A0A80EC00962D48 /* KissMeTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 341F5EA92A0A80EC00962D48 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 341F5EB32A0A80EC00962D48 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 341F5EA72A0A80EC00962D48 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 341F5EFB2A10909D00962D48 /* LoginResult.swift in Sources */, + 341F5F032A11A2BC00962D48 /* Credential.swift in Sources */, + 341F5EB02A0A80EC00962D48 /* KissMe.docc in Sources */, + 341F5EFD2A10931B00962D48 /* DomesticStockSearch.swift in Sources */, + 341F5EE52A0F3EF400962D48 /* DomesticStock.swift in Sources */, + 341F5EF72A0F8B0500962D48 /* DomesticStockResult.swift in Sources */, + 341F5EF02A0F886600962D48 /* ForeignFutures.swift in Sources */, + 341F5EEC2A0F883900962D48 /* ForeignStock.swift in Sources */, + 341F5EFF2A10955D00962D48 /* OrderRequest.swift in Sources */, + 341F5EE92A0F87FB00962D48 /* DomesticStockPrice.swift in Sources */, + 341F5EEE2A0F884300962D48 /* ForeignStockPrice.swift in Sources */, + 341F5EDE2A0F300100962D48 /* Request.swift in Sources */, + 341F5F012A11155100962D48 /* DomesticStockSearchResult.swift in Sources */, + 341F5EF22A0F887200962D48 /* DomesticFutures.swift in Sources */, + 341F5EF92A0F907300962D48 /* DomesticStockPriceResult.swift in Sources */, + 341F5EE12A0F373B00962D48 /* Login.swift in Sources */, + 341F5EF52A0F891200962D48 /* KissAccount.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 341F5EB12A0A80EC00962D48 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 341F5EBB2A0A80EC00962D48 /* KissMeTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 341F5EB82A0A80EC00962D48 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 341F5EAA2A0A80EC00962D48 /* KissMe */; + targetProxy = 341F5EB72A0A80EC00962D48 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 341F5EBD2A0A80EC00962D48 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 341F5EBE2A0A80EC00962D48 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 341F5EC02A0A80EC00962D48 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = NYU8YAYHF8; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.3; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = com.ened.KissMe; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 341F5EC12A0A80EC00962D48 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = NYU8YAYHF8; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.3; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = com.ened.KissMe; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 341F5EC32A0A80EC00962D48 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = NYU8YAYHF8; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MACOSX_DEPLOYMENT_TARGET = 13.3; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.ened.KissMeTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 341F5EC42A0A80EC00962D48 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = NYU8YAYHF8; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MACOSX_DEPLOYMENT_TARGET = 13.3; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.ened.KissMeTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 341F5EA52A0A80EC00962D48 /* Build configuration list for PBXProject "KissMe" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 341F5EBD2A0A80EC00962D48 /* Debug */, + 341F5EBE2A0A80EC00962D48 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 341F5EBF2A0A80EC00962D48 /* Build configuration list for PBXNativeTarget "KissMe" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 341F5EC02A0A80EC00962D48 /* Debug */, + 341F5EC12A0A80EC00962D48 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 341F5EC22A0A80EC00962D48 /* Build configuration list for PBXNativeTarget "KissMeTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 341F5EC32A0A80EC00962D48 /* Debug */, + 341F5EC42A0A80EC00962D48 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 341F5EA22A0A80EC00962D48 /* Project object */; +} diff --git a/KissMe.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/KissMe.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/KissMe.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/KissMe.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/KissMe.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/KissMe.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/KissMe.xcworkspace/contents.xcworkspacedata b/KissMe.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..292f961 --- /dev/null +++ b/KissMe.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/KissMe.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/KissMe.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/KissMe.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/KissMe/Common/Credential.swift b/KissMe/Common/Credential.swift new file mode 100644 index 0000000..7ada307 --- /dev/null +++ b/KissMe/Common/Credential.swift @@ -0,0 +1,71 @@ +// +// Credential.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/15. +// + +import Foundation + + +public protocol Credential { + var isMock: Bool { get } + var accountNo: String { get } + var appKey: String { get } + var appSecret: String { get } +} + +extension String { + var digitsOnly: String { filter { ("0"..."9").contains($0) } } +} + + +public struct KissCredential: Credential, Codable { + public let isMock: Bool + public let accountNo: String + public let appKey: String + public let appSecret: String + + public init(isMock: Bool, accountNo: String, appKey: String, appSecret: String) throws { + let digitAccountNo = accountNo.digitsOnly + guard digitAccountNo.count == 10 else { + throw GeneralError.invalidAccountNo + } + + self.isMock = isMock + self.accountNo = digitAccountNo + self.appKey = appKey + self.appSecret = appSecret + } + + public init(jsonUrl: URL) throws { + do { + let data = try Data(contentsOf: jsonUrl, options: .uncached) + let jsonData = try JSONDecoder().decode(KissCredential.self, from: data) + try self.init(isMock: jsonData.isMock, + accountNo: jsonData.accountNo, + appKey: jsonData.appKey, + appSecret: jsonData.appSecret) + } catch { + throw error + } + } + + public init(isMock: Bool) throws { + let serverFileName = isMock ? "mock-server.json": "real-server.json" + let path = "\(FileManager.default.currentDirectoryPath)/\(serverFileName)" + let jsonUrl = URL(filePath: path) + + try self.init(jsonUrl: jsonUrl) + } +} + + +extension AuthRequest { + + public var domain: String { + credential.isMock ? + "https://openapivts.koreainvestment.com:29443": + "https://openapi.koreainvestment.com:9443" + } +} diff --git a/KissMe/Common/OrderRequest.swift b/KissMe/Common/OrderRequest.swift new file mode 100644 index 0000000..9ed0e1b --- /dev/null +++ b/KissMe/Common/OrderRequest.swift @@ -0,0 +1,66 @@ +// +// OrderRequest.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/14. +// + +import Foundation + + +public protocol OrderRequest: TokenRequest { + var accountNo: String { get } +} + + +extension OrderRequest { + public var accountNo: String { + credential.accountNo + } + + var accountNo8: String { + String(accountNo.prefix(8)) + } + + var accountNo2: String { + String(accountNo.suffix(2)) + } +} + + +public enum OrderType { + case buy + case sell +} + + +public enum OrderDivision { + /// 지정가 + case limits + + /// 시장가 + case marketPrice + + var code: String { + switch self { + case .limits: return "00" + case .marketPrice: return "01" + } + } +} + + +public enum OrderRevisionType { + /// 정정 + case modify + + /// 취소 + case cancel + + var code: String { + switch self { + case .modify: return "01" + case .cancel: return "02" + } + } +} diff --git a/KissMe/Common/Request.swift b/KissMe/Common/Request.swift new file mode 100644 index 0000000..067ff0e --- /dev/null +++ b/KissMe/Common/Request.swift @@ -0,0 +1,160 @@ +// +// Request.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/13. +// + +import Foundation + + +public enum Method { + case `get` + case post +} + +extension Method: CustomStringConvertible { + public var description: String { + switch self { + case .get: return "GET" + case .post: return "POST" + } + } +} + +public enum GeneralError: Error { + case noServerJsonFile + case invalidAccessToken + case invalidAccountNo + case unsupportedQueryAtMockServer +} + +public enum QueryError: Error { + case invalidUrl + case invalidJson + case missingData +} + + +public protocol Request { + associatedtype KResult: Decodable + + var isMockAvailable: Bool { get } + var domain: String { get } + var url: String { get } + var userAgent: String { get } + + var method: Method { get } + var header: [String: String?] { get } + var body: [String: Any] { get } + var result: KResult? { get set } + var timeout: TimeInterval { get } +} + + +public protocol AuthRequest: Request { + var credential: Credential { get } +} + + +public protocol TokenRequest: AuthRequest { + var accessToken: String { get } +} + + +extension Request { + public var isMockAvailable: Bool { true } + + public var userAgent: String { + //"KissMe Agent" + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + } + + public var method: Method { .get } + public var header: [String: String?] { [:] } +} + + +extension AuthRequest { + + public var timeout: TimeInterval { + 15 + } + + var queryUrl: URL? { + URL(string: domain + url) + } + + func query(completion: @escaping (Result) -> Void) { + guard let url = queryUrl else { + completion(.failure(QueryError.invalidUrl)) + return + } + + if isMockAvailable == false, credential.isMock == true { + completion(.failure(GeneralError.unsupportedQueryAtMockServer)) + return + } + + let jsonBody: Data? + do { + jsonBody = try JSONSerialization.data(withJSONObject: body, options: .prettyPrinted) + } catch { + completion(.failure(QueryError.invalidJson)) + return + } + + + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: timeout) + + request.httpMethod = String(describing: method) + + switch method { + case .post: + request.httpBody = jsonBody + request.setValue("\(jsonBody?.count ?? 0)", forHTTPHeaderField: "Content-Length") + + case .get: + var queryItems = [URLQueryItem]() + for item in body { + if let value = item.value as? String { + queryItems.append(URLQueryItem(name: item.key, value: value)) + } + } + request.url?.append(queryItems: queryItems) + } + + request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type") + request.setValue("JSON", forHTTPHeaderField: "Format") + request.setValue(userAgent, forHTTPHeaderField: "User-Agent") + + for field in header { + request.setValue(field.value, forHTTPHeaderField: field.key) + } + + URLSession.shared.dataTask(with: request) { (data, response, error) in + if let error = error { + completion(.failure(error)) + return + } + + guard let data = data else { + completion(.failure(QueryError.missingData)) + return + } + + let stringData = String(data: data, encoding: .utf8) ?? "" + print(stringData) + + do { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .useDefaultKeys + + let result = try decoder.decode(KResult.self, from: data) + completion(.success(result)) + } catch { + completion(.failure(error)) + } + }.resume() + } +} diff --git a/KissMe/Domestic/DomesticFutures.swift b/KissMe/Domestic/DomesticFutures.swift new file mode 100644 index 0000000..8869cfe --- /dev/null +++ b/KissMe/Domestic/DomesticFutures.swift @@ -0,0 +1,13 @@ +// +// DomesticFutures.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/13. +// + +import Foundation + + +extension Domestic { + +} diff --git a/KissMe/Domestic/DomesticStock.swift b/KissMe/Domestic/DomesticStock.swift new file mode 100644 index 0000000..0e6a5ea --- /dev/null +++ b/KissMe/Domestic/DomesticStock.swift @@ -0,0 +1,330 @@ +// +// DomesticStock.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/13. +// + +import Foundation + + +public struct Domestic { + + /// 국내주식주문 - 주식주문(현금) + /// + public struct StockOrderRequest: OrderRequest { + public typealias KResult = OrderResult + + public var url: String { "/uapi/domestic-stock/v1/trading/order-cash" } + public var method: Method { .post } + + public var header: [String: String?] { + [ + "authorization": "Bearer \(accessToken)", + "appkey": credential.appKey, + "appsecret": credential.appSecret, + "tr_id": trId, + ] + } + public var body: [String: Any] { + [ + "CANO": accountNo8, + "ACNT_PRDT_CD": accountNo2, + "PDNO": productNo, + "ORD_DVSN": orderDivision.code, + "ORD_QTY": orderQuantity, + "ORD_UNPR": String(orderPrice), + ] + } + public var result: KResult? = nil + public let credential: Credential + + + private var trId: String { + if credential.isMock { + switch orderType { + case .buy: return "VTTC0802U" + case .sell: return "VTTC0801U" + } + } + else { + switch orderType { + case .buy: return "TTTC0802U" + case .sell: return "TTTC0801U" + } + } + } + + public let accessToken: String + let productNo: String + + let orderType: OrderType + let orderDivision: OrderDivision + let orderQuantity: Int + let orderPrice: Int + + public init(credential: Credential, accessToken: String, contract: Contract) { + self.credential = credential + self.accessToken = accessToken + self.productNo = contract.productNo + self.orderType = contract.orderType + self.orderDivision = contract.orderDivision + self.orderQuantity = contract.orderQuantity + self.orderPrice = contract.orderPrice + } + } + + + /// 국내주식주문 - 주식주문(정정취소)[v1_국내주식-003] + /// + public struct StockOrderRevisionRequest: OrderRequest { + public typealias KResult = OrderRevisionResult + + public var url: String { + "/uapi/domestic-stock/v1/trading/order-rvsecncl" + } + public var method: Method { .post } + + public var header: [String: String?] { + [ + "authorization": "Bearer \(accessToken)", + "appkey": credential.appKey, + "appsecret": credential.appSecret, + "tr_id": trId, + ] + } + public var body: [String: Any] { + [ + "CANO": accountNo8, + "ACNT_PRDT_CD": accountNo2, + "KRX_FWDG_ORD_ORGNO": orderOrganizationNo, + "ORGN_ODNO": orderNo, + "ORD_DVSN": orderDivision.code, + "RVSE_CNCL_DVSN_CD": orderRevisionType.code, + "ORD_QTY": String(orderQuantity), + "ORD_UNPR": String(orderPrice), + "QTY_ALL_ORD_YN": isAllQuantity ? "Y": "N" + ] + } + public var result: KResult? = nil + public let credential: Credential + + + private var trId: String { + if credential.isMock { + return "VTTC0803U" + } + else { + return "TTTC0803U" + } + } + + public let accessToken: String + let productNo: String + + let orderOrganizationNo: String + let orderNo: String + let orderDivision: OrderDivision + let orderRevisionType: OrderRevisionType + let orderQuantity: Int + let orderPrice: Int + let isAllQuantity: Bool + + public init(credential: Credential, accessToken: String, productNo: String, orderOrganizationNo: String, orderNo: String, orderDivision: OrderDivision, orderRevisionType: OrderRevisionType, orderQuantity: Int = 0, orderPrice: Int) { + self.credential = credential + self.accessToken = accessToken + self.productNo = productNo + self.orderOrganizationNo = orderOrganizationNo + self.orderNo = orderNo + self.orderDivision = orderDivision + self.orderRevisionType = orderRevisionType + self.orderQuantity = orderQuantity + self.orderPrice = orderPrice + self.isAllQuantity = (orderQuantity == 0) + } + } + + + /// 국내주식주문 - 주식잔고조회[v1_국내주식-006] + /// + public struct StockBalanceRequest: OrderRequest { + public typealias KResult = String + + public var url: String { + "/uapi/domestic-stock/v1/trading/inquire-balance" + } + public var method: Method { .get } + + public var header: [String: String?] { + [ + "authorization": "Bearer \(accessToken)", + "appkey": credential.appKey, + "appsecret": credential.appSecret, + "tr_id": trId, + ] + } + public var body: [String: Any] { + [ + "CANO": accountNo8, + "ACNT_PRDT_CD": accountNo2, + "AFHR_FLPR_YN": "N", + "OFL_YN": " ", + "INQR_DVSN": "02", + "UNPR_DVSN": "01", + "FUND_STTL_ICLD_YN": "N", + "FNCG_AMT_AUTO_RDPT_YN": "N", + "PRCS_DVSN": "01", + "CTX_AREA_FK100": " ", + "CTX_AREA_NK100": " ", + ] + } + public var result: KResult? = nil + public let credential: Credential + + + private var trId: String { + if credential.isMock { + return "VTTC0803U" + } + else { + return "TTTC0803U" + } + } + + public let accessToken: String + + public init(credential: Credential, accessToken: String) { + self.credential = credential + self.accessToken = accessToken + } + } + + + /// 국내주식주문 - 매수가능조회[v1_국내주식-007] + /// + public struct StockPossibleOrderRequest: OrderRequest { + public typealias KResult = OrderPossibleResult + + public var url: String { + "/uapi/domestic-stock/v1/trading/inquire-psbl-order" + } + public var method: Method { .get } + + public var header: [String: String?] { + [ + "authorization": "Bearer \(accessToken)", + "appkey": credential.appKey, + "appsecret": credential.appSecret, + "tr_id": trId, + ] + } + public var body: [String: Any] { + [ + "CANO": accountNo8, + "ACNT_PRDT_CD": accountNo2, + "PDNO": productNo, + "ORD_UNPR": String(orderPrice), + "ORD_DVSN": orderDivision.code, + "CMA_EVLU_AMT_ICLD_YN": "", + "OVRS_ICLD_YN": "", + ] + } + public var result: KResult? = nil + public let credential: Credential + + + private var trId: String { + if credential.isMock { + return "VTTC8908R" + } + else { + return "TTTC8908R" + } + } + + public let accessToken: String + let productNo: String + + let orderDivision: OrderDivision + let orderPrice: Int + + public init(credential: Credential, accessToken: String, productNo: String, orderDivision: OrderDivision, orderPrice: Int) { + self.credential = credential + self.accessToken = accessToken + self.productNo = productNo + self.orderDivision = orderDivision + self.orderPrice = orderPrice + } + } +} + + +public struct Contract { + public let productNo: String + public let orderType: OrderType + public let orderDivision: OrderDivision + public let orderQuantity: Int + public let orderPrice: Int +} + + +// MARK: Stock Order +extension KissAccount { + + public func orderStock(contract: Contract, completion: @escaping (Result) -> Void) { + guard let accessToken = accessToken else { + completion(.failure(GeneralError.invalidAccessToken)) + return + } + + let request = Domestic.StockOrderRequest(credential: credential, accessToken: accessToken, contract: contract) + request.query { result in + switch result { + case .success(let result): + completion(.success(true)) + case .failure(let error): + completion(.failure(error)) + } + } + } + + + public func cancelOrder(completion: @escaping (Result) -> Void) { + guard let accessToken = accessToken else { + completion(.failure(GeneralError.invalidAccessToken)) + return + } + + // TODO: work + } + + + public func changeOrder(completion: @escaping (Result) -> Void) { + guard let accessToken = accessToken else { + completion(.failure(GeneralError.invalidAccessToken)) + return + } + + // TODO: work + } + + + public func getStockBalance(completion: @escaping (Result) -> Void) { + guard let accessToken = accessToken else { + completion(.failure(GeneralError.invalidAccessToken)) + return + } + + // TODO: work + } + + + public func canOrderStock(completion: @escaping (Result) -> Void) { + guard let accessToken = accessToken else { + completion(.failure(GeneralError.invalidAccessToken)) + return + } + + // TODO: work + } +} diff --git a/KissMe/Domestic/DomesticStockPrice.swift b/KissMe/Domestic/DomesticStockPrice.swift new file mode 100644 index 0000000..05f00cc --- /dev/null +++ b/KissMe/Domestic/DomesticStockPrice.swift @@ -0,0 +1,128 @@ +// +// DomesticStockPrice.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/13. +// + +import Foundation + + +extension Domestic { + + /// 국내주식시세 - 주식현재가 시세[v1_국내주식-008] + /// + public struct StockCurrentPriceRequest: TokenRequest { + public typealias KResult = CurrentPriceResult + + public var url: String { + "/uapi/domestic-stock/v1/quotations/inquire-price" + } + public var method: Method { .get } + + public var header: [String: String?] { + [ + "authorization": "Bearer \(accessToken)", + "appkey": credential.appKey, + "appsecret": credential.appSecret, + "tr_id": trId, + ] + } + public var body: [String: Any] { + [ + "FID_COND_MRKT_DIV_CODE": "J", + "FID_INPUT_ISCD": productNo, + ] + } + public var result: KResult? = nil + public let credential: Credential + + + private var trId: String { + "FHKST01010100" + } + + public let accessToken: String + let productNo: String + + public init(credential: Credential, accessToken: String, productNo: String) { + self.credential = credential + self.accessToken = accessToken + self.productNo = productNo + } + } + + + /// 국내주식시세 - 주식당일분봉조회[v1_국내주식-022] + /// + public struct StockTodayMinutePriceRequest: TokenRequest { + public typealias KResult = MinutePriceResult + + public var url: String { + "/uapi/domestic-stock/v1/quotations/inquire-time-itemchartprice" + } + public var method: Method { .get } + + public var header: [String: String?] { + [ + "authorization": "Bearer \(accessToken)", + "appkey": credential.appKey, + "appsecret": credential.appSecret, + "tr_id": trId, + "custtype": CustomerType.personal.rawValue, + ] + } + public var body: [String: Any] { + [ + "FID_ETC_CLS_CODE": "", + "FID_COND_MRKT_DIV_CODE": "J", + "FID_INPUT_ISCD": productNo, + "FID_INPUT_HOUR_1": startTime, + "FID_PW_DATA_INCU_YN": "N", + ] + } + public var result: KResult? = nil + public let credential: Credential + + + private var trId: String { + "FHKST03010200" + } + + public let accessToken: String + let productNo: String + let startTime: String // HHMMSS + + public init(credential: Credential, accessToken: String, productNo: String, startTime: String) { + self.credential = credential + self.accessToken = accessToken + self.productNo = productNo + self.startTime = startTime + } + } +} + + +// MARK: Stock Price +extension KissAccount { + + + public func getCurrentPrice(completion: @escaping (Result) -> Void) { + guard let accessToken = accessToken else { + completion(.failure(GeneralError.invalidAccessToken)) + return + } + + // TODO: work + } + + + public func getMinutePrice(completion: @escaping (Result) -> Void) { + guard let accessToken = accessToken else { + completion(.failure(GeneralError.invalidAccessToken)) + return + } + + // TODO: work + } +} diff --git a/KissMe/Domestic/DomesticStockPriceResult.swift b/KissMe/Domestic/DomesticStockPriceResult.swift new file mode 100644 index 0000000..6066175 --- /dev/null +++ b/KissMe/Domestic/DomesticStockPriceResult.swift @@ -0,0 +1,502 @@ +// +// DomesticStockPriceResult.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/13. +// + +import Foundation + + +public enum YesNo: String, Codable { + case yes = "Y" + case no = "N" +} + + +/// 보증금 비율 구분 +public enum MarginalRateClass: String, Codable { + /// 20%, 30%, 40% + case _40 = "40" + /// 50% + case _50 = "50" + /// 60% + case _60 = "60" +} + + +/// 시장경고코드 +public enum MarketWarning: String, Codable { + /// 없음 + case none = "00" + /// 투자주의 + case investmentCaution = "01" + /// 투자경고 + case investmentWarning = "02" + /// 투자위험 + case investmentDanger = "03" +} + + +public struct CurrentPriceResult: Codable { + public let resultCode: String + public let messageCode: String + public let message: String + public let output: [OutputDetail]? + + private enum CodingKeys: String, CodingKey { + case resultCode = "rt_cd" + case messageCode = "msg_cd" + case message = "msg1" + case output + } + + /// 종목 상태 구분 + public enum StateClass: String, Codable { + /// 그외 + case etc = "00" + /// 관리종목 + case underAdmin = "51" + /// 투자의견 + case investmentOption = "52" + /// 투자경고 + case investmentWarning = "53" + /// 투자주의 + case investmentCaution = "54" + /// 신용가능 + case creditWorthy = "55" + /// 증거금 100% + case depositAll = "57" + /// 거래정지 + case tradingHalt = "58" + /// 단기과열 + case shortTermOverheating = "59" + } + + public struct OutputDetail: Codable { + /// 종목 상태 구분 코드 + public let itemStateCode: StateClass + + /// 증거금 비율 + public let marginalRate: String + + /// 대표 시장 한글 명 + public let koreanMarketName: String + + /// 신 고가 저가 구분 코드 + public let newHighLowPriceClassCode: String + + /// 업종 한글 종목명 + public let koreanBusinessTypeName: String + + /// 임시 정지 여부 + public let temporaryStopped: YesNo + + /// 시가 범위 연장 여부 + public let marketPriceRangeExtended: YesNo + + /// 종가 범위 연장 여부 + public let closingPriceRangeExtended: YesNo + + /// 신용 가능 여부 + public let creditAllowable: YesNo + + /// 보증금 비율 구분 코드 + public let marginalRateClassCode: MarginalRateClass + + /// ELW 발행 여부 + public let elwPublished: YesNo + + /// 주식 현재가 + public let currentStockPrice: String + + /// 전일 대비 + public let previousDayVariableRatio: String + + /// 전일 대비 부호 + public let previousDayVariableRatioSign: String + + /// 전일 대비율 + public let previousDayDiffRatio: String + + /// 누적 거래 대금 + public let accumulatedTradingAmount: String + + /// 누적 거래량 + public let accumulatedVolume: String + + /// 전일 대비 거래량 비율 + public let previousDayDiffVolumeRatio: String + + /// 주식 시가 + public let stockPrice: String + + /// 주식 최고가 + public let highestStockPrice: String + + /// 주식 최저가 + public let lowestStockPrice: String + + /// 주식 상한가 + public let maximumStockPrice: String + + /// 주식 하한가 + public let minimumStockPrice: String + + /// 주식 기준가 + public let standardStockPrice: String + + /// 가중 평균 주식 가격 + public let weightedAverageStockPrice: String + + /// HTS 외국인 소진율 + public let htsForeignRunoutRate: String + + /// 외국인 순매수 수량 + public let foreignNetBuyingQuantity: String + + /// 프로그램매매 순매수 수량 + public let programTradeNetBuyingQuantity: String + + /// 피벗 2차 디저항 가격 + public let pivotSecondDResistancePrice: String + + /// 피벗 1차 디저항 가격 + public let pivotFirstDResistancePrice: String + + /// 피벗 포인트 값 + public let pivotPointValue: String + + /// 피벗 1차 디지지 가격 + public let pivotFirstDSupportPrice: String + + /// 피벗 2차 디지지 가격 + public let pivotSecondDSupportPrice: String + + /// 디저항 값 + public let dResistanceValue: String + + /// 디지지 값 + public let dSupportValue: String + + /// 자본금 + public let capital: String + + /// 제한 폭 가격 + public let limitWidthPrice: String + + /// 주식 액면가 + public let stockFacePrice: String + + /// 주식 대용가 + public let stockSubstitudePrice: String + + /// 호가단위 + public let askingPriceUnit: String + + /// HTS 매매 수량 단위 값 + public let htsDealQuantityUnit: String + + /// 상장 주수 + public let listedStockCount: String + + /// HTS 시가총액 + public let htsTotalMarketValue: String + + /// PER + public let per: String + + /// PBR + public let pbr: String + + /// 결산 월 + public let settlingMonth: String + + /// 거래량 회전율 + public let volumeTurnoverRate: String + + /// EPS + public let eps: String + + /// BPS + public let bps: String + + /// 250일 최고가 + public let day250HighestPrice: String + + /// 250일 최고가 일자 + public let day250HighestPriceDate: String + + /// 250일 최고가 대비 현재가 비율 + public let day250HighestPriceDiffRatio: String + + /// 250일 최저가 + public let day250LowestPrice: String + + /// 250일 최저가 일자 + public let day250LowestPriceDate: String + + /// 250일 최저가 대비 현재가 비율 + public let day250LowestPriceDiffRatio: String + + /// 주식 연중 최고가 + public let annualHighestPrice: String + + /// 연중 최고가 대비 현재가 비율 + public let annualHighestPriceDiffRatio: String + + /// 연중 최고가 일자 + public let annualHighestPriceDate: String + + /// 주식 연중 최저가 + public let annualLowestPrice: String + + /// 연중 최저가 대비 현재가 비율 + public let annualLowestPriceDiffRatio: String + + /// 연중 최저가 일자 + public let annualLowestPriceDate: String + + /// 52주일 최고가 + public let week52HighestPrice: String + + /// 52주일 최고가 대비 현재가 대비 + public let week52HighestPriceDiffRatio: String + + /// 52주일 최고가 일자 + public let week52HighestPriceDate: String + + /// 52주일 최저가 + public let week52LowestPrice: String + + /// 52주일 최저가 대비 현재가 대비 + public let week52LowestPriceDiffRatio: String + + /// 52주일 최저가 일자 + public let week52LowestPriceDate: String + + /// 전체 융자 잔고 비율 + public let totalOutstandingloanRate: String + + /// 공매도가능여부 + public let shortSellingAllowable: YesNo + + /// 주식 단축 종목코드 + public let shortProductCode: String + + /// 액면가 통화명 + public let facePriceCurrency: String + + /// 자본금 통화명 + public let capitalCurrency: String + + /// 접근도 + public let approachRate: String + + /// 외국인 보유 수량 + public let foreignHoldQuantity: String + + /// VI적용구분코드 + public let viClassCode: String + + /// 시간외단일가VI적용구분코드 + public let viClassCodeForOvertimeMarketPrice: String + + /// 최종 공매도 체결 수량 + public let lastShortSellingConclusionQuantity: String + + /// 투자유의여부 + public let investmentCareful: YesNo + + /// 시장경고코드 + public let marketWarningCode: MarketWarning + + /// 단기과열여부 + public let shortOverheated: YesNo + + private enum CodingKeys: String, CodingKey { + case itemStateCode = "iscd_stat_cls_code" + case marginalRate = "marg_rate" + case koreanMarketName = "rprs_mrkt_kor_name" + case newHighLowPriceClassCode = "new_hgpr_lwpr_cls_code" + + case koreanBusinessTypeName = "bstp_kor_isnm" + case temporaryStopped = "temp_stop_yn" + case marketPriceRangeExtended = "oprc_rang_cont_yn" + case closingPriceRangeExtended = "clpr_rang_cont_yn" + case creditAllowable = "crdt_able_yn" + case marginalRateClassCode = "grmn_rate_cls_code" + case elwPublished = "elw_pblc_yn" + + case currentStockPrice = "stck_prpr" + case previousDayVariableRatio = "prdy_vrss" + case previousDayVariableRatioSign = "prdy_vrss_sign" + case previousDayDiffRatio = "prdy_ctrt" + case accumulatedTradingAmount = "acml_tr_pbmn" + case accumulatedVolume = "acml_vol" + case previousDayDiffVolumeRatio = "prdy_vrss_vol_rate" + + case stockPrice = "stck_oprc" + case highestStockPrice = "stck_hgpr" + case lowestStockPrice = "stck_lwpr" + case maximumStockPrice = "stck_mxpr" + case minimumStockPrice = "stck_llam" + case standardStockPrice = "stck_sdpr" + case weightedAverageStockPrice = "wghn_avrg_stck_prc" + + case htsForeignRunoutRate = "hts_frgn_ehrt" + case foreignNetBuyingQuantity = "frgn_ntby_qty" + case programTradeNetBuyingQuantity = "pgtr_ntby_qty" + case pivotSecondDResistancePrice = "pvt_scnd_dmrs_prc" + case pivotFirstDResistancePrice = "pvt_frst_dmrs_prc" + case pivotPointValue = "pvt_pont_val" + case pivotFirstDSupportPrice = "pvt_frst_dmsp_prc" + case pivotSecondDSupportPrice = "pvt_scnd_dmsp_prc" + case dResistanceValue = "dmrs_val" + case dSupportValue = "dmsp_val" + + case capital = "cpfn" + case limitWidthPrice = "rstc_wdth_prc" + case stockFacePrice = "stck_fcam" + case stockSubstitudePrice = "stck_sspr" + case askingPriceUnit = "aspr_unit" + case htsDealQuantityUnit = "hts_deal_qty_unit_val" + case listedStockCount = "lstn_stcn" + case htsTotalMarketValue = "hts_avls" + + case per + case pbr + case settlingMonth = "stac_month" + case volumeTurnoverRate = "vol_tnrt" + case eps + case bps + + case day250HighestPrice = "d250_hgpr" + case day250HighestPriceDate = "d250_hgpr_date" + case day250HighestPriceDiffRatio = "d250_hgpr_vrss_prpr_rate" + case day250LowestPrice = "d250_lwpr" + case day250LowestPriceDate = "d250_lwpr_date" + case day250LowestPriceDiffRatio = "d250_lwpr_vrss_prpr_rate" + + case annualHighestPrice = "stck_dryy_hgpr" + case annualHighestPriceDiffRatio = "dryy_hgpr_vrss_prpr_rate" + case annualHighestPriceDate = "dryy_hgpr_date" + case annualLowestPrice = "stck_dryy_lwpr" + case annualLowestPriceDiffRatio = "dryy_lwpr_vrss_prpr_rate" + case annualLowestPriceDate = "dryy_lwpr_date" + + case week52HighestPrice = "w52_hgpr" + case week52HighestPriceDiffRatio = "w52_hgpr_vrss_prpr_ctrt" + case week52HighestPriceDate = "w52_hgpr_date" + case week52LowestPrice = "w52_lwpr" + case week52LowestPriceDiffRatio = "w52_lwpr_vrss_prpr_ctrt" + case week52LowestPriceDate = "w52_lwpr_date" + + case totalOutstandingloanRate = "whol_loan_rmnd_rate" + case shortSellingAllowable = "ssts_yn" + case shortProductCode = "stck_shrn_iscd" + case facePriceCurrency = "fcam_cnnm" + case capitalCurrency = "cpfn_cnnm" + case approachRate = "apprch_rate" + + case foreignHoldQuantity = "frgn_hldn_qty" + case viClassCode = "vi_cls_code" + case viClassCodeForOvertimeMarketPrice = "ovtm_vi_cls_code" + case lastShortSellingConclusionQuantity = "last_ssts_cntg_qty" + case investmentCareful = "invt_caful_yn" + case marketWarningCode = "mrkt_warn_cls_code" + case shortOverheated = "short_over_yn" + } + } +} + + +public struct MinutePriceResult: Codable { + public let resultCode: String + public let messageCode: String + public let message: String + public let output1: [OutputSummary]? + public let output2: [OutputPrice]? + + private enum CodingKeys: String, CodingKey { + case resultCode = "rt_cd" + case messageCode = "msg_cd" + case message = "msg1" + case output1 + case output2 + } + + public struct OutputSummary: Codable { + /// 전일 대비 + public let previousDayVariableRatio: String + + /// 전일 대비 부호 + public let previousDayVariableRatioSign: String + + /// 전일 대비율 + public let previousDayDiffRatio: String + + /// 주식 전일 종가 + public let previousDayStockClosingPrice: String + + /// 누적 거래량 + public let accumulatedVolume: String + + /// 누적 거래 대금 + public let accumulatedTradingAmount: String + + /// HTS 한글 종목명 + public let htsProductName: String + + /// 주식 현재가 + public let currentStockPrice: String + + private enum CodingKeys: String, CodingKey { + case previousDayVariableRatio = "prdy_vrss" + case previousDayVariableRatioSign = "prdy_vrss_sign" + case previousDayDiffRatio = "prdy_ctrt" + case previousDayStockClosingPrice = "stck_prdy_clpr" + case accumulatedVolume = "acml_vol" + case accumulatedTradingAmount = "acml_tr_pbmn" + case htsProductName = "hts_kor_isnm" + case currentStockPrice = "stck_prpr" + } + } + + public struct OutputPrice: Codable { + /// 주식 영업 일자 + public let stockBusinessDate: String + + /// 주식 체결 시간 + public let stockConclusionTime: String + + /// 누적 거래 대금 + public let accumulatedTradingAmount: String + + /// 주식 현재가 + public let currentStockPrice: String + + /// 주식 시가2 + public let secondStockPrice: String + + /// 주식 최고가 + public let highestStockPrice: String + + /// 주식 최저가 + public let lowestStockPrice: String + + /// 체결 거래량 + public let conclusionVolume: String + + private enum CodingKeys: String, CodingKey { + case stockBusinessDate = "stck_bsop_date" + case stockConclusionTime = "stck_cntg_hour" + case accumulatedTradingAmount = "acml_tr_pbmn" + case currentStockPrice = "stck_prpr" + case secondStockPrice = "stck_oprc" + case highestStockPrice = "stck_hgpr" + case lowestStockPrice = "stck_lwpr" + case conclusionVolume = "cntg_vol" + } + } +} diff --git a/KissMe/Domestic/DomesticStockResult.swift b/KissMe/Domestic/DomesticStockResult.swift new file mode 100644 index 0000000..3ab106e --- /dev/null +++ b/KissMe/Domestic/DomesticStockResult.swift @@ -0,0 +1,136 @@ +// +// DomesticStockResult.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/13. +// + +import Foundation + + +public enum CustomerType: String { + case corporation = "B" + case personal = "P" +} + + +public struct OrderResult: Codable { + public let resultCode: String + public let messageCode: String + public let message: String + public let output: [OutputDetail]? + + private enum CodingKeys: String, CodingKey { + case resultCode = "rt_cd" + case messageCode = "msg_cd" + case message = "msg" + case output + } + + public struct OutputDetail: Codable { + public let orderOrganizationNo: String + public let orderNo: String + public let orderTime: String + + private enum CodingKeys: String, CodingKey { + case orderOrganizationNo = "KRX_FWDG_ORD_ORGNO" + case orderNo = "ODNO" + case orderTime = "ORD_TMD" + } + } +} + + +public struct OrderRevisionResult: Codable { + public let resultCode: String + public let messageCode: String + public let message: String + public let output: [OutputDetail]? + + private enum CodingKeys: String, CodingKey { + case resultCode = "rt_cd" + case messageCode = "msg_cd" + case message = "msg1" + case output + } + + public struct OutputDetail: Codable { + public let orderOrganizationNo: String + public let orderNo: String + public let orderTime: String + + private enum CodingKeys: String, CodingKey { + case orderOrganizationNo = "KRX_FWDG_ORD_ORGNO" + case orderNo = "ODNO" + case orderTime = "ORD_TMD" + } + } +} + + +public struct OrderPossibleResult: Codable { + public let resultCode: String + public let messageCode: String + public let message: String + public let output: [OutputPossibleDetail]? + + private enum CodingKeys: String, CodingKey { + case resultCode = "rt_cd" + case messageCode = "msg_cd" + case message = "msg1" + case output + } + + public struct OutputPossibleDetail: Codable { + /// 주문가능현금 + public let orderPossibleCash: String + + /// 주문가능대용 + public let orderPossibleSubstitute: String + + /// 재사용가능금액 + public let reusePossibleAmount: String + + /// 펀드환매대금 + public let fundRepurchaseChanges: String + + /// 가능수량계산단가 + public let possibleQuantityCalcPrice: String + + /// 미수없는매수금액 + public let nonrechargeableBuyAmount: String + + /// 미수없는매수수량 + public let nonrechargeableBuyQuantity: String + + /// 최대매수금액 + public let maxBuyAmount: String + + /// 최대매수수량 + public let maxBuyQuantity: String + + /// CMA평가금액 + public let cmaEvaluationAmount: String + + /// 해외재사용금액원화 + public let overseasReuseAmountWon: String + + /// 주문가능외화금액원화 + public let orderPossibleForeignCurrencyAmountWon: String + + private enum CodingKeys: String, CodingKey { + case orderPossibleCash = "ord_psbl_cash" + case orderPossibleSubstitute = "ord_psbl_sbst" + case reusePossibleAmount = "ruse_psbl_amt" + case fundRepurchaseChanges = "fund_rpch_chgs" + case possibleQuantityCalcPrice = "psbl_qty_calc_unpr" + case nonrechargeableBuyAmount = "nrcvb_buy_amt" + case nonrechargeableBuyQuantity = "nrcvb_buy_qty" + case maxBuyAmount = "max_buy_amt" + case maxBuyQuantity = "max_buy_qty" + case cmaEvaluationAmount = "cma_evlu_amt" + case overseasReuseAmountWon = "ovrs_re_use_amt_wcrc" + case orderPossibleForeignCurrencyAmountWon = "ord_psbl_frcr_amt_wcrc" + } + } +} diff --git a/KissMe/Domestic/DomesticStockSearch.swift b/KissMe/Domestic/DomesticStockSearch.swift new file mode 100644 index 0000000..3cf9c4f --- /dev/null +++ b/KissMe/Domestic/DomesticStockSearch.swift @@ -0,0 +1,117 @@ +// +// DomesticStockSearch.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/14. +// + +import Foundation + + +public enum DivisionClassCode: String { + /// 전체 + case all = "0" + /// 보통주 + case equity = "1" + /// 우선주 + case preferredStock = "2" +} + + +public enum BelongClassCode: String { + /// 평균거래량 + case averageVolume = "0" + /// 거래증가율 + case volumeIncreaseRate = "1" + /// 평균거래회전율 + case averageVolumeTurnoverRate = "2" + /// 거래금액순 + case transactionValue = "3" + /// 평균거래금액회전율 + case averageTransactionValueTurnoverRate = "4" +} + + +extension Domestic { + + /// 국내주식시세 - 거래량순위[v1_국내주식-047] + /// + public struct StockVolumeRankRequest: TokenRequest { + public typealias KResult = VolumeRankResult + + public var isMockAvailable: Bool { false } + + public var url: String { + "/uapi/domestic-stock/v1/quotations/volume-rank" + } + public var method: Method { .get } + + public var header: [String: String?] { + [ + "authorization": "Bearer \(accessToken)", + "appkey": credential.appKey, + "appsecret": credential.appSecret, + "tr_id": trId, + "custtype": CustomerType.personal.rawValue, + ] + } + public var body: [String: Any] { + [ + "FID_COND_MRKT_DIV_CODE": "J", + "FID_COND_SCR_DIV_CODE": "20171", + "FID_INPUT_ISCD": "0000", // TODO: 기타(업종코드) + "FID_DIV_CLS_CODE": divisionClass.rawValue, + "FID_BLNG_CLS_CODE": belongClass.rawValue, + "FID_TRGT_CLS_CODE": "000000000", + "FID_TRGT_EXLS_CLS_CODE": "000000", + "FID_INPUT_PRICE_1": "", + "FID_INPUT_PRICE_2": "1000000", + "FID_VOL_CNT": "", + "FID_INPUT_DATE_1": "", + ] + } + public var result: KResult? = nil + public let credential: Credential + + + private var trId: String { + "FHPST01710000" + } + + public let accessToken: String + let divisionClass: DivisionClassCode + let belongClass: BelongClassCode + + public init(credential: Credential, accessToken: String, divisionClass: DivisionClassCode, belongClass: BelongClassCode) { + self.credential = credential + self.accessToken = accessToken + self.divisionClass = divisionClass + self.belongClass = belongClass + } + } +} + + +// MARK: Stock Search +extension KissAccount { + + public func getVolumeRanking(divisionClass: DivisionClassCode, belongClass: BelongClassCode) async throws -> VolumeRankResult { + + return try await withUnsafeThrowingContinuation { continuation in + guard let accessToken = accessToken else { + continuation.resume(throwing: GeneralError.invalidAccessToken) + return + } + + let request = Domestic.StockVolumeRankRequest(credential: credential, accessToken: accessToken, divisionClass: divisionClass, belongClass: belongClass) + request.query { result in + switch result { + case .success(let result): + continuation.resume(returning: result) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } +} diff --git a/KissMe/Domestic/DomesticStockSearchResult.swift b/KissMe/Domestic/DomesticStockSearchResult.swift new file mode 100644 index 0000000..2810491 --- /dev/null +++ b/KissMe/Domestic/DomesticStockSearchResult.swift @@ -0,0 +1,106 @@ +// +// DomesticStockSearchResult.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/14. +// + +import Foundation + + +public struct VolumeRankResult: Codable { + public let resultCode: String + public let messageCode: String + public let message: String + public let output1: [OutputDetail]? + + private enum CodingKeys: String, CodingKey { + case resultCode = "rt_cd" + case messageCode = "msg_cd" + case message = "msg1" + case output1 = "Output1" + } + + public struct OutputDetail: Codable { + /// HTS 한글 종목명 + public let htsProductName: String + + /// 유가증권 단축 종목코드 + public let shortProductNo: String + + /// 데이터 순위 + public let dataRank: String + + /// 주식 현재가 + public let currentStockPrice: String + + /// 전일 대비 부호 + public let previousDayVariableRatioSign: String + + /// 전일 대비 + public let previousDayVariableRatio: String + + /// 전일 대비율 + public let previousDayDiffRatio: String + + /// 누적 거래량 + public let accumulatedVolume: String + + /// 전일 거래량 + public let previousDayVolume: String + + /// 상장 주수 + public let listedStockCount: String + + /// 평균 거래량 + public let averageVolume: String + + /// N일전종가대비현재가대비율 + public let nDayBeforeClosingPriceDiffRatio: String + + /// 거래량증가율 + public let volumeIncreaseRate: String + + /// 거래량 회전율 + public let volumeTurnoverRate: String + + /// N일 거래량 회전율 + public let nDayVolumeTurnoverRate: String + + /// 평균 거래 대금 + public let averageTradingAmount: String + + /// 거래대금회전율 + public let tradingAmountTurnoverRate: String + + /// N일 거래대금 회전율 + public let nDayTradingAmountTurnoverRate: String + + /// 누적 거래 대금 + public let accumulatedTradingAmount: String + + private enum CodingKeys: String, CodingKey { + case htsProductName = "hts_kor_isnm" + case shortProductNo = "mksc_shrn_iscd" + case dataRank = "data_rank" + case currentStockPrice = "stck_prpr" + + case previousDayVariableRatioSign = "prdy_vrss_sign" + case previousDayVariableRatio = "prdy_vrss" + case previousDayDiffRatio = "prdy_ctrt" + case accumulatedVolume = "acml_vol" + case previousDayVolume = "prdy_vol" + case listedStockCount = "lstn_stcn" + case averageVolume = "avrg_vol" + + case nDayBeforeClosingPriceDiffRatio = "n_befr_clpr_vrss_prpr_rate" + case volumeIncreaseRate = "vol_inrt" + case volumeTurnoverRate = "vol_tnrt" + case nDayVolumeTurnoverRate = "nday_vol_tnrt" + case averageTradingAmount = "avrg_tr_pbmn" + case tradingAmountTurnoverRate = "tr_pbmn_tnrt" + case nDayTradingAmountTurnoverRate = "nday_tr_pbmn_tnrt" + case accumulatedTradingAmount = "acml_tr_pbmn" + } + } +} diff --git a/KissMe/Foreign/ForeignFutures.swift b/KissMe/Foreign/ForeignFutures.swift new file mode 100644 index 0000000..d188de3 --- /dev/null +++ b/KissMe/Foreign/ForeignFutures.swift @@ -0,0 +1,10 @@ +// +// ForeignFutures.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/13. +// + +import Foundation + +// TODO: WORK diff --git a/KissMe/Foreign/ForeignStock.swift b/KissMe/Foreign/ForeignStock.swift new file mode 100644 index 0000000..7c7993c --- /dev/null +++ b/KissMe/Foreign/ForeignStock.swift @@ -0,0 +1,10 @@ +// +// ForeignStock.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/13. +// + +import Foundation + +// TODO: WORK diff --git a/KissMe/Foreign/ForeignStockPrice.swift b/KissMe/Foreign/ForeignStockPrice.swift new file mode 100644 index 0000000..ac38f6e --- /dev/null +++ b/KissMe/Foreign/ForeignStockPrice.swift @@ -0,0 +1,10 @@ +// +// ForeignStockPrice.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/13. +// + +import Foundation + +// TODO: WORK diff --git a/KissMe/KissAccount.swift b/KissMe/KissAccount.swift new file mode 100644 index 0000000..4c42836 --- /dev/null +++ b/KissMe/KissAccount.swift @@ -0,0 +1,28 @@ +// +// KissAccount.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/13. +// + +import Foundation + + +public class KissAccount { + + let credential: Credential + + var accessTokenLock = NSLock() + var accessToken: String? + + public init(credential: Credential) { + self.credential = credential + self.accessToken = nil + } + + func setAccessToken(_ accessToken: String?) { + accessTokenLock.lock() + self.accessToken = accessToken + accessTokenLock.unlock() + } +} diff --git a/KissMe/KissMe.docc/KissMe.md b/KissMe/KissMe.docc/KissMe.md new file mode 100755 index 0000000..6dbed65 --- /dev/null +++ b/KissMe/KissMe.docc/KissMe.md @@ -0,0 +1,13 @@ +# ``KissMe`` + +Summary + +## Overview + +Text + +## Topics + +### Group + +- ``Symbol`` \ No newline at end of file diff --git a/KissMe/KissMe.h b/KissMe/KissMe.h new file mode 100644 index 0000000..4e3bf1d --- /dev/null +++ b/KissMe/KissMe.h @@ -0,0 +1,18 @@ +// +// KissMe.h +// KissMe +// +// Created by ened-book-m1 on 2023/05/09. +// + +#import + +//! Project version number for KissMe. +FOUNDATION_EXPORT double KissMeVersionNumber; + +//! Project version string for KissMe. +FOUNDATION_EXPORT const unsigned char KissMeVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/KissMe/Login/Login.swift b/KissMe/Login/Login.swift new file mode 100644 index 0000000..3daa59a --- /dev/null +++ b/KissMe/Login/Login.swift @@ -0,0 +1,175 @@ +// +// Login.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/13. +// + +import Foundation + + +///. OAuth인증 - 접근토큰발급(P)[인증-001] +/// +public struct LoginAuthRequest: AuthRequest { + public typealias KResult = TokenResult + + public var url: String { "/oauth2/tokenP" } + public var method: Method { .post } + + public var body: [String: Any] { + [ + "grant_type": "client_credentials", + "appkey": credential.appKey, + "appsecret": credential.appSecret + ] + } + public var result: KResult? = nil + public var credential: Credential + + public init(credential: Credential) { + self.credential = credential + } +} + + +/// OAuth인증 - 접근토큰폐기(P)[인증-002] +/// +public struct LogoutAuthRequest: AuthRequest { + public typealias KResult = MessageResult + + public var url: String { "/oauth2/revokeP" } + public var method: Method { .post } + + public var body: [String: Any] { + [ + "appkey": credential.appKey, + "appsecret": credential.appSecret, + "token": accessToken + ] + } + public var result: KResult? = nil + public var credential: Credential + + let accessToken: String + + public init(credential: Credential, accessToken: String) { + self.credential = credential + self.accessToken = accessToken + } +} + + +/// OAuth인증 - Hashkey +/// +public struct HashKeyAuthRequest: AuthRequest { + public typealias KResult = HashKeyResult + + public var url: String { "/uapi/hashkey" } + public var method: Method { .post } + + public var header: [String: String?] { + [ + "appkey": credential.appKey, + "appsecret": credential.appSecret + ] + } + public var body: [String: Any] { + [ + "JsonBody": jsonBody + ] + } + public var result: KResult? = nil + public let credential: Credential + + let jsonBody: String + + public init(credential: Credential, jsonBody: String) { + self.credential = credential + self.jsonBody = jsonBody + } +} + + +/// OAuth인증 - 실시간 (웹소켓) 접속키 발급[실시간-000] +/// +public struct ApprovalKeyAuthRequest: AuthRequest { + public typealias KResult = ApprovalKeyResult + + public var url: String { "/oauth2/Approval" } + public var method: Method { .post } + + public var body: [String: Any] { + [ + "grant_type": "client_credentials", + "appkey": credential.appKey, + "appsecret": credential.appSecret + ] + } + public var result: KResult? = nil + public let credential: Credential + + public init(credential: Credential) { + self.credential = credential + } +} + + +extension KissAccount { + + public func login() async throws -> Bool { + return try await withUnsafeThrowingContinuation { continuation in + + let request = LoginAuthRequest(credential: credential) + request.query { result in + switch result { + case .success(let result): + self.setAccessToken(result.accessToken) + continuation.resume(returning: true) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + + public func logout() async throws -> Bool { + return try await withUnsafeThrowingContinuation { continuation in + guard let accessToken = accessToken else { + continuation.resume(throwing: GeneralError.invalidAccessToken) + return + } + + let request = LogoutAuthRequest(credential: credential, accessToken: accessToken) + request.query { result in + switch result { + case .success(let result): + if result.code == 200 { + self.setAccessToken(nil) + continuation.resume(returning: true) + } + else { + let error = NSError(domain: "KissLogout", code: result.code, userInfo: [ + NSLocalizedDescriptionKey: result.message + ]) + continuation.resume(throwing: error) + } + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + + public func hashKey(json: String, completion: @escaping (Result) -> Void) { + let request = HashKeyAuthRequest(credential: credential, jsonBody: json) + request.query(completion: completion) + } + + + public func approvalKey(completion: @escaping (Result) -> Void) { + let request = ApprovalKeyAuthRequest(credential: credential) + request.query(completion: completion) + } +} diff --git a/KissMe/Login/LoginResult.swift b/KissMe/Login/LoginResult.swift new file mode 100644 index 0000000..19be44d --- /dev/null +++ b/KissMe/Login/LoginResult.swift @@ -0,0 +1,42 @@ +// +// LoginResult.swift +// KissMe +// +// Created by ened-book-m1 on 2023/05/14. +// + +import Foundation + + +public struct TokenResult: Codable { + public let accessToken: String + public let tokenType: String + public let expiresIn: Int + + private enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + case tokenType = "token_type" + case expiresIn = "expires_in" + } +} + + +public struct MessageResult: Codable { + public let code: Int + public let message: String +} + + +public struct HashKeyResult: Codable { + public let body: String + public let hash: String +} + + +public struct ApprovalKeyResult: Codable { + public let approvalKey: String + + private enum CodingKeys: String, CodingKey { + case approvalKey = "approval_key" + } +} diff --git a/KissMeConsole/KissMeConsole.xcodeproj/project.pbxproj b/KissMeConsole/KissMeConsole.xcodeproj/project.pbxproj new file mode 100644 index 0000000..48645b1 --- /dev/null +++ b/KissMeConsole/KissMeConsole.xcodeproj/project.pbxproj @@ -0,0 +1,305 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 341F5ED42A0A8B9000962D48 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5ED32A0A8B9000962D48 /* main.swift */; }; + 341F5EDC2A0A8C4600962D48 /* KissMe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 341F5EDB2A0A8C4600962D48 /* KissMe.framework */; }; + 341F5F052A13B82F00962D48 /* test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 341F5F042A13B82F00962D48 /* test.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 341F5ECE2A0A8B9000962D48 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 341F5ED02A0A8B9000962D48 /* KissMeConsole */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = KissMeConsole; sourceTree = BUILT_PRODUCTS_DIR; }; + 341F5ED32A0A8B9000962D48 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 341F5EDB2A0A8C4600962D48 /* KissMe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KissMe.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 341F5F042A13B82F00962D48 /* test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = test.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 341F5ECD2A0A8B9000962D48 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 341F5EDC2A0A8C4600962D48 /* KissMe.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 341F5EC72A0A8B9000962D48 = { + isa = PBXGroup; + children = ( + 341F5ED22A0A8B9000962D48 /* KissMeConsole */, + 341F5ED12A0A8B9000962D48 /* Products */, + 341F5EDA2A0A8C4600962D48 /* Frameworks */, + ); + sourceTree = ""; + }; + 341F5ED12A0A8B9000962D48 /* Products */ = { + isa = PBXGroup; + children = ( + 341F5ED02A0A8B9000962D48 /* KissMeConsole */, + ); + name = Products; + sourceTree = ""; + }; + 341F5ED22A0A8B9000962D48 /* KissMeConsole */ = { + isa = PBXGroup; + children = ( + 341F5ED32A0A8B9000962D48 /* main.swift */, + 341F5F042A13B82F00962D48 /* test.swift */, + ); + path = KissMeConsole; + sourceTree = ""; + }; + 341F5EDA2A0A8C4600962D48 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 341F5EDB2A0A8C4600962D48 /* KissMe.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 341F5ECF2A0A8B9000962D48 /* KissMeConsole */ = { + isa = PBXNativeTarget; + buildConfigurationList = 341F5ED72A0A8B9000962D48 /* Build configuration list for PBXNativeTarget "KissMeConsole" */; + buildPhases = ( + 341F5ECC2A0A8B9000962D48 /* Sources */, + 341F5ECD2A0A8B9000962D48 /* Frameworks */, + 341F5ECE2A0A8B9000962D48 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = KissMeConsole; + productName = KissMeConsole; + productReference = 341F5ED02A0A8B9000962D48 /* KissMeConsole */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 341F5EC82A0A8B9000962D48 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1430; + LastUpgradeCheck = 1430; + TargetAttributes = { + 341F5ECF2A0A8B9000962D48 = { + CreatedOnToolsVersion = 14.3; + }; + }; + }; + buildConfigurationList = 341F5ECB2A0A8B9000962D48 /* Build configuration list for PBXProject "KissMeConsole" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 341F5EC72A0A8B9000962D48; + productRefGroup = 341F5ED12A0A8B9000962D48 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 341F5ECF2A0A8B9000962D48 /* KissMeConsole */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 341F5ECC2A0A8B9000962D48 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 341F5ED42A0A8B9000962D48 /* main.swift in Sources */, + 341F5F052A13B82F00962D48 /* test.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 341F5ED52A0A8B9000962D48 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 13.3; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 341F5ED62A0A8B9000962D48 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 13.3; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 341F5ED82A0A8B9000962D48 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = NYU8YAYHF8; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 341F5ED92A0A8B9000962D48 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = NYU8YAYHF8; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 341F5ECB2A0A8B9000962D48 /* Build configuration list for PBXProject "KissMeConsole" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 341F5ED52A0A8B9000962D48 /* Debug */, + 341F5ED62A0A8B9000962D48 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 341F5ED72A0A8B9000962D48 /* Build configuration list for PBXNativeTarget "KissMeConsole" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 341F5ED82A0A8B9000962D48 /* Debug */, + 341F5ED92A0A8B9000962D48 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 341F5EC82A0A8B9000962D48 /* Project object */; +} diff --git a/KissMeConsole/KissMeConsole.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/KissMeConsole/KissMeConsole.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/KissMeConsole/KissMeConsole.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/KissMeConsole/KissMeConsole.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/KissMeConsole/KissMeConsole.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/KissMeConsole/KissMeConsole.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/KissMeConsole/KissMeConsole.xcodeproj/xcshareddata/xcschemes/KissMeConsole.xcscheme b/KissMeConsole/KissMeConsole.xcodeproj/xcshareddata/xcschemes/KissMeConsole.xcscheme new file mode 100644 index 0000000..3f1b6fa --- /dev/null +++ b/KissMeConsole/KissMeConsole.xcodeproj/xcshareddata/xcschemes/KissMeConsole.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/KissMeConsole/KissMeConsole/main.swift b/KissMeConsole/KissMeConsole/main.swift new file mode 100644 index 0000000..60c78ab --- /dev/null +++ b/KissMeConsole/KissMeConsole/main.swift @@ -0,0 +1,128 @@ +// +// main.swift +// KissMeConsole +// +// Created by ened-book-m1 on 2023/05/09. +// + +import Foundation +import KissMe + + +class KissConsole { + var credential: Credential? = nil + var account: KissAccount? = nil + + enum KissCommand: String { + case quit = "quit" + case loginMock = "login mock" + case loginReal = "login real" + case logout = "logout" + case search = "search" + } + + var isLogined: Bool { + account != nil + } + + + func onLoginMock() async { + guard !isLogined else { + print("Already loginged") + return + } + + do { + credential = try KissCredential(isMock: true) + account = KissAccount(credential: credential!) + if try await account!.login() { + print("Success") + } + } catch { + print("\(error)") + } + } + + + func onLoginReal() async { + guard !isLogined else { + print("Already loginged") + return + } + + do { + credential = try KissCredential(isMock: false) + account = KissAccount(credential: credential!) + if try await account!.login() { + print("Success") + } + } catch { + print("\(error)") + } + } + + + func onLogout() async { + guard isLogined else { + print("Already logout") + return + } + + do { + _ = try await account?.logout() + credential = nil + account = nil + } catch { + print("\(error)") + } + } + + + func onSearch(_ arg: [String]) async { + guard isLogined else { + print("Already logout") + return + } + + do { + _ = try await account?.getVolumeRanking(divisionClass: .all, belongClass: .averageVolume) + } catch { + print("\(error)") + } + } + + + func run() { + print("Enter command:") + let semaphore = DispatchSemaphore(value: 0) + var loop = true + + while loop { + guard let line = readLine(strippingNewline: true) else { + continue + } + let args = line.split(separator: " ") + let single = args.prefix(upTo: min(2, args.count)).joined(separator: " ") + let cmd = KissCommand(rawValue: single) + + Task { + switch cmd { + case .quit: break + case .loginMock: await onLoginMock() + case .loginReal: await onLoginReal() + case .logout: await onLogout() + case .search: await onSearch(args.suffix(from: 1).map { String($0) }) + default: + print("Unknown command: \(single)") + } + semaphore.signal() + } + semaphore.wait() + + loop = (cmd != .quit) + } + } +} + + +KissConsole().run() diff --git a/KissMeConsole/KissMeConsole/test.swift b/KissMeConsole/KissMeConsole/test.swift new file mode 100644 index 0000000..a00e4e2 --- /dev/null +++ b/KissMeConsole/KissMeConsole/test.swift @@ -0,0 +1,61 @@ +// +// test.swift +// KissMeConsole +// +// Created by ened-book-m1 on 2023/05/16. +// + +import Foundation +import KissMe + + +func test_login_get_volume_ranking() async { + let isMock = false + let credential: Credential + + do { + /// isMock 이 true 이면, mock-server.json 에서 로딩하여 생성 + /// isMock 이 false 이면, real-server.json 에서 로딩하여 생성 + /// server.json 의 구조 + /// { + /// "isMock": true, + /// "accountNo": "12345678-90" + /// "appKey": "xxxxxxxx", + /// "appSecret": "yyyyyyyyyyyyyyyyyy" + /// } + /// + credential = try KissCredential(isMock: isMock) + } catch { + print("\(error)") + return + } + + + let account = KissAccount(credential: credential) + do { + if try await account.login() { + let result = try await account.getVolumeRanking(divisionClass: .equity, belongClass: .averageVolume) + print("\(result)") + } + } catch { + print("\(error)") + return + } +} + + +func test_json_result() { + let str = "{\"rt_cd\":\"1\",\"msg_cd\":\"EGW00205\",\"msg1\":\"credentials_type이 유효하지 않습니다.(Bearer)\"}" + + do { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .useDefaultKeys + + let data = Data(str.utf8) + let result = try decoder.decode(VolumeRankResult.self, from: data) + print("\(result)") + } + catch { + print("\(error)") + } +} diff --git a/KissMeTests/KissMeTests.swift b/KissMeTests/KissMeTests.swift new file mode 100644 index 0000000..6f5c18d --- /dev/null +++ b/KissMeTests/KissMeTests.swift @@ -0,0 +1,36 @@ +// +// KissMeTests.swift +// KissMeTests +// +// Created by ened-book-m1 on 2023/05/09. +// + +import XCTest +@testable import KissMe + +class KissMeTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..628002c --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +## About KissMe + +KissMe 는 KIS API 를 연동한 swift 라이브러리입니다. + +KissMeConsole 은 command line 에서 인터렉티브 명령어로 API 호출을 테스트해볼 수 있는 도구입니다. + +KissCredential 에서 사용하는 json 의 양식은 다음과 같습니다. + +```json +{ + "isMock": false, + "accountNo": "12345678-90", + "appKey": "xxxxxxxxxxx", + "appSecret": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy" +} +``` + +* `isMock` 값이 `true` 이면 모의서버, `false` 이면 실전서버를 의미합니다. +* `accountNo` 에는 계좌번호를 의미합니다. 8-2 형태의 숫자로 입력합니다. +* `appKey` 는 한국투자증권 홈페이지에서 발급받은 appkey 입니다. +* `appSecret` 는 한국투자증권 홈페이지에서 발급받은 appsecret 입니다.