finchal / app /anchors.py
SeaWolf-AI's picture
intraday time axis; baselines seeded at season open
83d14d7 verified
Raw
History Blame Contribute Delete
4.58 kB
# -*- coding: utf-8 -*-
"""๊ธฐ์ค€์„  ๋ด‡ โ€” ์ฐธ๊ฐ€์ž์™€ ๊ฐ™์€ ํ‘œ์— ๋†“์ผ ์ฒ™๋„.
ODC๊ฐ€ ์Šน์ธ์•ฝ๊ณผ ๋ถˆํ™œ์„ฑ ํ™”ํ•ฉ๋ฌผ์„ ๊ฐ™์€ ํ‘œ์— ๋„ฃ์–ด ์ฒ™๋„๋ฅผ ๋ณด์—ฌ์ฃผ๋“ฏ,
๋ฆฌ๋”๋ณด๋“œ์— ๊ธฐ์ค€ ์ „๋žต์„ ํ•จ๊ป˜ ์˜ฌ๋ฆฐ๋‹ค. ์ฒ™๋„๊ฐ€ ์—†๋Š” ์ˆœ์œ„ํ‘œ๋Š” ์ฝ์„ ์ˆ˜ ์—†๋‹ค.
๐Ÿ”ด ์ฃผ์ตœ์ธก ๋ชจ๋ธ์€ ๋„ฃ์ง€ ์•Š๋Š”๋‹ค.
์ด ์ฑŒ๋ฆฐ์ง€์˜ ๋ช…๋ถ„์€ "๋ˆ„๊ฐ€ ๋” ๋‚˜์€ ์˜ˆ์ธก ๋ชจ๋ธ์„ ๋งŒ๋“œ๋Š”๊ฐ€"์ด์ง€
"์ฃผ์ตœ์ž๊ฐ€ ์ œ์ผ ์ž˜ํ•œ๋‹ค"๊ฐ€ ์•„๋‹ˆ๋‹ค. ์ฃผ์ตœ์ž๊ฐ€ ์ž๊ธฐ ๋ชจ๋ธ์„ ์ฐธ๊ฐ€์ž ํ‘œ์— ์˜ฌ๋ฆฌ๋ฉด
๊ทธ ๋ช…๋ถ„์ด ํ๋ ค์ง„๋‹ค. ๊ธฐ์ค€์„ ์€ ์ฒ™๋„์ด์ง€ ๊ฒฝ์Ÿ์ž๊ฐ€ ์•„๋‹ˆ๋‹ค.
๊ธฐ์ค€์„  ์…‹ โ€” ์ „๋ถ€ ๊ณต๊ฐœ๋œ ๊ต๊ณผ์„œ ๊ธฐ๋ฒ•์ด๋‹ค
BUYHOLD ํ•ญ์ƒ +1. ์ด๊ฑธ ๋ชป ์ด๊ธฐ๋ฉด ์•„๋ฌด ์˜๋ฏธ ์—†๋‹ค
VOLTARGET ์ง€์ˆ˜๊ฐ€์ค‘ ๋ณ€๋™์„ฑ์œผ๋กœ ํฌ๊ธฐ๋งŒ ์กฐ์ ˆ. ๋ฐฉํ–ฅ์€ ๋กฑ ๊ณ ์ •
RANDOM ๋งค์ผ -1/0/+1 ๋ฌด์ž‘์œ„. ๋ฐ”๋‹ฅ
๐Ÿ”‘ VOLTARGET ์€ ๋ฐฉํ–ฅ์„ ์˜ˆ์ธกํ•˜์ง€ ์•Š๋Š”๋‹ค.
๋ฐฉํ–ฅ ์˜ˆ์ธก์€ 60๊ฐœ ๋ณ€์ˆ˜๋กœ ๋‹ค์„ฏ ๋‹จ๊ณ„์— ๊ฑธ์ณ ์‹œํ—˜ํ–ˆ๊ณ  ์ „๋ถ€ ์˜ˆ์ธก๋ ฅ์ด ์—†์—ˆ๋‹ค.
๊ธฐ์ค€์„ ์ด ๋ฐฉํ–ฅ์„ ๋งžํžˆ๋Š” ์ฒ™ํ•˜๋ฉด, ๊ธฐ๊ฐํ•œ ๊ฒƒ์„ ๊ณต๊ฐœ๋ฉด์— ์˜ฌ๋ฆฌ๋Š” ์…ˆ์ด ๋œ๋‹ค.
๊ทธ๋ž˜์„œ ์ด ๊ธฐ์ค€์„ ์€ '์–ผ๋งˆ๋‚˜ ํฌ๊ฒŒ ๋“ค ๊ฒƒ์ธ๊ฐ€'๋งŒ ์ •ํ•œ๋‹ค.
"""
import os, sys
import numpy as np, pandas as pd
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path[:0] = [HERE, os.path.dirname(HERE)]
import ledger as L
ANCHORS = {
"BUYHOLD": "๋‹จ์ˆœ ๋ณด์œ ",
"VOLTARGET": "๋ณ€๋™์„ฑ ํƒ€๊ฒŸํŒ…",
"RANDOM": "๋ฌด์ž‘์œ„",
}
SPAN = 60
def position(kind, px, seed=0):
"""์˜ค๋Š˜ ์žก์„ ํฌ์ง€์…˜. -1 ~ +1."""
if kind == "BUYHOLD":
return 1.0
if kind == "RANDOM":
# ๋‚ ์งœ๋ฅผ ์‹œ๋“œ์— ๋„ฃ์–ด ๊ฐ™์€ ๋‚  ๊ฐ™์€ ๊ฐ’ โ€” ์ฑ„์ ์ด ์žฌํ˜„ ๊ฐ€๋Šฅํ•ด์•ผ ํ•œ๋‹ค
g = np.random.default_rng(
int(pd.Timestamp.utcnow().strftime("%Y%m%d")) * 97 + seed)
return float(g.choice([-1.0, 0.0, 1.0]))
s = np.log(px).diff().ewm(span=SPAN).std().dropna()
if len(s) < SPAN * 2:
return 0.0
# ๋ชฉํ‘œ ๋ณ€๋™์„ฑ = ๊ทธ ์ข…๋ชฉ ๊ณผ๊ฑฐ ์ค‘์•™๊ฐ’. ๋ณ€๋™์„ฑ์ด ๋‚ฎ์œผ๋ฉด ํฌ๊ฒŒ, ๋†’์œผ๋ฉด ์ž‘๊ฒŒ ๋“ ๋‹ค.
target = float(s.iloc[:len(s) // 2].median())
return float(max(0.0, min(1.0, target / float(s.iloc[-1]))))
def run(assets, prices_fn, dry=False, opens=None):
"""๊ธฐ์ค€์„  ์…‹์˜ ์˜ค๋Š˜ ํฌ์ง€์…˜์„ ๊ณ„์‚ฐํ•ด ์›์žฅ์— ๋„ฃ๋Š”๋‹ค.
๐Ÿ”ด ๊ธฐ์ค€์„ ์€ **์‹œ์ฆŒ ๊ฐœ๋ง‰ ์‹œ์ ๋ถ€ํ„ฐ** ์กด์žฌํ•ด์•ผ ํ•œ๋‹ค. ์„œ๋ฒ„๊ฐ€ ์ฒ˜์Œ ๋œฌ ์ˆœ๊ฐ„์—
๋„ฃ์œผ๋ฉด ๊ทธ ์ „ ๊ตฌ๊ฐ„์ด ๋น„์–ด, ๋‹จ์ˆœ ๋ณด์œ ๊ฐ€ ์‹œ์žฅ์„ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ผ๊ฐ€์ง€ ์•Š๋Š”๋‹ค.
์‹ค์ œ๋กœ ๊ฐœ๋ง‰์ผ์— ์‹œ์žฅ โˆ’0.82% ์ธ๋ฐ ๋‹จ์ˆœ ๋ณด์œ ๊ฐ€ +0.12% ๋กœ ๋‚˜์™”๋‹ค โ€”
๊ธฐ์ค€์„ ์ด ํ•˜๋ฝ ๊ตฌ๊ฐ„์„ ํ†ต์งธ๋กœ ๋น„์ผœ๊ฐ„ ๊ฒƒ์ด๋ผ ์ฒ™๋„ ๊ตฌ์‹ค์„ ๋ชป ํ•œ๋‹ค.
๐Ÿ”‘ ๊ฐ’์ด ๊ทธ๋Œ€๋กœ๋ฉด ๋‹ค์‹œ ์“ฐ์ง€ ์•Š๋Š”๋‹ค. ์„œ๋ฒ„๊ฐ€ ์žฌ์‹œ์ž‘ํ•  ๋•Œ๋งˆ๋‹ค ๊ฐ™์€ ํฌ์ง€์…˜์„
์Œ“์œผ๋ฉด ์›์žฅ์ด ๋ถ€ํ’€๊ณ , ๋ฌด์—‡์ด ์‹ค์ œ ํŒ๋‹จ ๋ณ€๊ฒฝ์ธ์ง€ ์ฝ์„ ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค.
"""
if not dry:
for name in ANCHORS:
L.register(name, ANCHORS[name], "anchor", list(assets))
out = []
for i, a in enumerate(assets):
px = prices_fn(a)
if px is None or len(px) < SPAN * 2:
continue
for name in ANCHORS:
p = round(position(name, px, seed=i), 4)
note = ""
if not dry:
if opens and not L.positions(a, name):
# ๊ฐœ๋ง‰ ์‹œ์ ์— ๋จผ์ € ์‹ฌ๋Š”๋‹ค. ๊ทธ๋ž˜์•ผ ์ฒ™๋„๊ฐ€ ์ „ ๊ตฌ๊ฐ„์„ ๋ฎ๋Š”๋‹ค.
L.seed(name, a, p, opens, "anchor-open")
note = "๊ฐœ๋ง‰ ์‹œ์  ๊ธฐ๋ก"
last = L.latest(a, name)
if last is None or abs(last["p"] - p) > 1e-9:
L.submit(name, a, p, "anchor")
note = note or "๊ฐฑ์‹ "
else:
note = note or "๋ณ€๋™ ์—†์Œ"
out.append({"anchor": name, "asset": a, "position": p, "note": note})
return out
if __name__ == "__main__":
import main as M
dry = "--dry" in sys.argv
rows = run(list(M.ASSETS), lambda a: M.prices(a, "1d", 1500), dry=dry)
print("๊ธฐ์ค€์„  ํฌ์ง€์…˜ %s\n" % ("(๋ชจ์˜)" if dry else "์ œ์ถœ ์™„๋ฃŒ"))
by = {}
for r in rows:
by.setdefault(r["asset"], {})[r["anchor"]] = r["position"]
print(" %-6s" % "์ข…๋ชฉ" + "".join("%12s" % k for k in ANCHORS))
for a, v in by.items():
print(" %-6s" % a + "".join("%12.3f" % v.get(k, 0) for k in ANCHORS))