-
Notifications
You must be signed in to change notification settings - Fork 15
/
type_strategy.go
207 lines (164 loc) · 8.21 KB
/
type_strategy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package ftfw
import (
"github.com/ghts/ghts/lib"
)
func New전략_실행기[T팩터 T팩터_데이터, T재무 T재무_데이터](인수 I전략_인수[T팩터, T재무]) I전략_실행기 {
s := new(S전략_실행기[T팩터, T재무])
s.I전략_인수 = 인수
return s
}
// 다음의 경우에 수정 없이 재사용하는 것을 목표로 함.
// 1. 'I전략_인수'에서 '데이터_처리기'와 '포트폴리오'만 교체해 주면, 백테스트에서 검증된 전략 소스 코드를 그대로 '실제 매매'에 적용.
// 2. 'I전략_인수'에서 '복합_등급_계산_함수'만 교체하면 여러가지 전략에 적용.
// 3. 데이터 공급자를 교체해도 큰 수정 없이 사용.
// 향후 손절/익절 규칙 또한 인수에서 교체할 수 있도록 하는 것을 고려해야 할 듯.
type S전략_실행기[T팩터 T팩터_데이터, T재무 T재무_데이터] struct {
I전략_인수[T팩터, T재무]
}
func (s *S전략_실행기[T팩터, T재무]) G실행() (에러 error) {
defer lib.S예외처리{M에러: &에러}.S실행()
if s.G포트폴리오().G리밸런싱_필요(s.G계좌번호(), s.G리밸런싱_주기()) {
s.g리밸런싱_실행()
} else {
s.g손절_익절_확인()
}
return nil
}
func (s *S전략_실행기[T팩터, T재무]) g손절_익절_확인() (에러 error) {
defer lib.S예외처리{M에러: &에러}.S실행()
데이터_처리기 := s.G데이터_처리기()
포트폴리오 := s.G포트폴리오()
리밸런싱_기준_금액 := 0.0
현재_평가액 := 0.0
for _, 종목코드 := range 포트폴리오.G보유_종목_코드_모음() {
현재가 := 데이터_처리기.G현재가(종목코드)
기준가 := 포트폴리오.G리밸런싱_기준_가격(종목코드)
const 손절_기준_퍼센트 = 12 // 개별 종목 손절
손절_기준_만족 := 손절_기준_퍼센트 > 0 && 현재가 < 기준가*(1-손절_기준_퍼센트)
익절_기준_만족 := false // TODO : 트레일링 스탑
if 손절_기준_만족 {
포트폴리오.S손절(종목코드)
} else if 익절_기준_만족 {
포트폴리오.S익절(종목코드) // 포트폴리오.S부분_익절(종목코드, 50)
}
기준_수량 := float64(포트폴리오.G리밸런싱_기준_수량(종목코드))
리밸런싱_기준_금액 += 기준가 * 기준_수량
현재_평가액 += 현재가 * 기준_수량
}
const 전체_손절_기준_퍼센트 = 7 // 전략별 보유 종목 전체 손절
전체_손절_기준_만족 := 전체_손절_기준_퍼센트 > 0 && 현재_평가액 < 리밸런싱_기준_금액*(1-전체_손절_기준_퍼센트)
if 전체_손절_기준_만족 {
포트폴리오.S전체_손절()
}
return nil
}
func (s *S전략_실행기[T팩터, T재무]) g리밸런싱_실행() (에러 error) {
데이터_처리기 := s.G데이터_처리기()
팩터_데이터_처리기 := s.G팩터_데이터_처리기()
포트폴리오 := s.G포트폴리오()
전략_식별_문자열 := s.G전략_식별_문자열()
기준_수량 := s.G종목_수량()
v := 팩터_데이터_처리기.G필터_정렬_처리기()
v.S전처리(데이터_처리기)
v.S등급_산출()
if 수량 := v.Len(); 수량 < 기준_수량*4 {
lib.F문자열_출력("'%v' 후보 종목 수량 너무 적음 : %v개", 전략_식별_문자열, 수량)
}
s.G복합_등급_계산_함수()(v)
f급등_종목_제외(v, s.I전략_인수)
f급락_종목_제외(v, s.I전략_인수)
f상장_주식_수량_확인(v, s.I전략_인수)
if s.G버퍼_퍼센트() > 0 {
f버퍼룰_적용(v, s.I전략_인수)
}
f복합_등급_기준_정렬(v).S상위_N개(기준_수량) // 최종 종목 선정
//lib.F문자열_출력("%v %v 종목 선정 완료.", 전략명, lib.F지금().Format("15:04"))
return 포트폴리오.S리밸런싱_실행(v.G종목코드_모음())
}
func (s *S전략_실행기[T팩터, T재무]) g필터_정렬_도우미(값_모음 []T팩터) *S필터_정렬_처리기[T팩터] {
v := new(S필터_정렬_처리기[T팩터])
v.M저장소 = 값_모음
return v
}
func f급등_종목_제외[T팩터 T팩터_데이터, T재무 T재무_데이터](v *S필터_정렬_처리기[T팩터], 인수 I전략_인수[T팩터, T재무]) {
if 인수.G급등_종목_제외() {
v.S필터(func(값 T팩터) bool {
return !값.G최근_급등()
})
}
}
func f급락_종목_제외[T팩터 T팩터_데이터, T재무 T재무_데이터](v *S필터_정렬_처리기[T팩터], 인수 I전략_인수[T팩터, T재무]) {
if 인수.G급락_종목_제외() {
v.S필터(func(값 T팩터) bool {
return !값.G최근_급락()
})
}
}
// 상장 주식 수량은 '시가 총액'과 PER, PBR 같은 각종 가치 지표를 계산하는 데 사용되는 중요한 데이터이지만,
// 지속적으로 추적 및 갱신하기 어려울 수 있다.
// 그럴 경우에는 리밸런싱 할 때 최종적으로 확인해서, 장 주식 수량이 바뀐 종목은 후보에서 제외함으로서, 잘못된 종목을 선정하지 않도록 한다.
// 단, 실제 매매에서만 중요할 뿐, 백테스트에서는 별 필요없을 수 있음.
// 백테스트 코드와 실제 매매 코드의 변경 사항을 최소화 하기 위해서 남겨둠.
func f상장_주식_수량_확인[T팩터 T팩터_데이터, T재무 T재무_데이터](v *S필터_정렬_처리기[T팩터], 인수 I전략_인수[T팩터, T재무]) {
종목_수량 := 인수.G종목_수량()
배수 := (100 + 인수.G버퍼_퍼센트()) / 100
버퍼_포함_수량 := int(float64(종목_수량) * 배수)
// 실제 매매 상황에서는 상장 주식 수량을 질의하는 데 상당한 시간이 소요되므로,
// 소요되는 연산량을 줄이기 위해서 후보 종목을 추린다.
f복합_등급_기준_정렬(v).S상위_N개(버퍼_포함_수량 + 10)
// 상장 주식 수량 확인
종목코드_맵 := lib.F2맵(인수.G데이터_처리기().S상장_주식_수량_확인(v.G종목코드_모음()))
v.S필터(func(값 T팩터) bool {
_, 존재함 := 종목코드_맵[값.G종목코드()]
return 존재함
})
}
// 버퍼 범위 이내에 있는 종목은 유지해서 거래 비용 절감.
func f버퍼룰_적용[T팩터 T팩터_데이터, T재무 T재무_데이터](v *S필터_정렬_처리기[T팩터], 인수 I전략_인수[T팩터, T재무]) {
버퍼_퍼센트 := 인수.G버퍼_퍼센트()
포트폴리오 := 인수.G포트폴리오()
기준_수량 := 인수.G종목_수량()
버퍼_포함_수량 := f버퍼_포함_수량(기준_수량, 버퍼_퍼센트)
if 버퍼_퍼센트 < 0.001 {
return // 0%이면 버퍼룰 적용 안 함.
} else if 수량 := v.Len(); 수량 < 기준_수량 {
lib.F문자열_출력("'%v' : 후보 종목 부족. 버퍼룰 적용 보류. %v개", 인수.G전략_식별_문자열(), 수량)
return
} else if 수량 < 버퍼_포함_수량 {
배수 := (100 + 버퍼_퍼센트) / 100
lib.F문자열_출력("'%v' : 후보 종목 수량 %.1f배수 미만 : %v개", 인수.G전략_식별_문자열(), 배수, 수량)
}
f복합_등급_기준_정렬(v).S상위_N개(버퍼_포함_수량) // 버퍼 포함 후보 선정.
종목코드_맵 := make(map[string]lib.S비어있음)
// 보유 중인 종목은 우선적으로 선정.
if 보유_수량_맵, 에러 := 포트폴리오.G보유_수량_맵(); 에러 == nil {
for _, 종목코드 := range v.G종목코드_모음() {
if len(종목코드_맵) >= 기준_수량 {
break
} else if 보유_수량, 존재함 := 보유_수량_맵[종목코드]; 존재함 && 보유_수량 > 0 {
종목코드_맵[종목코드] = lib.S비어있음{}
}
}
}
// 신규 종목 추가.
for _, 종목코드 := range v.G종목코드_모음() {
if len(종목코드_맵) >= 기준_수량 {
break
} else {
종목코드_맵[종목코드] = lib.S비어있음{}
}
}
v.S필터(func(종목_데이터 T팩터) bool {
_, 존재함 := 종목코드_맵[종목_데이터.G종목코드()]
return 존재함
})
}
func f복합_등급_기준_정렬[T T팩터_데이터](v *S필터_정렬_처리기[T]) *S필터_정렬_처리기[T] {
return v.S정렬(func(s *S필터_정렬_처리기[T], i, j int) bool {
return s.M저장소[i].G복합_등급() < s.M저장소[j].G복합_등급()
})
}
func f버퍼_포함_수량(기준_수량 int, 버퍼_퍼센트 float64) int {
배수 := (100 + 버퍼_퍼센트) / 100
return int(float64(기준_수량) * 배수)
}